{"_id":"@appliqation/playwright-pom-adapter","_rev":"2-4a4418efc9dbe97baa306b9f4934154f","name":"@appliqation/playwright-pom-adapter","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@appliqation/playwright-pom-adapter","version":"0.1.0","keywords":["appliqation","testing","automation","playwright","page-object-model","codemod"],"author":{"name":"Appliqation Team"},"license":"MIT","_id":"@appliqation/playwright-pom-adapter@0.1.0","maintainers":[{"name":"archana6","email":"accounts@appliqation.io"}],"homepage":"https://github.com/appliqation/playwright-pom-adapter#readme","bugs":{"url":"https://github.com/appliqation/playwright-pom-adapter/issues"},"bin":{"appq-pom-convert":"src/cli/convert.js","appq-pom-sync-back":"src/cli/syncBack.js"},"dist":{"shasum":"ffbe18284c063bd65bcbefe6106a2b9ee572e7fe","tarball":"https://registry.npmjs.org/@appliqation/playwright-pom-adapter/-/playwright-pom-adapter-0.1.0.tgz","fileCount":17,"integrity":"sha512-zGE+Kd3ueaoXqKmwOKwfoUxkVjML0P3bdRrqnkgCvkALScUFlpILcHrsF1mpoqe2bFOkIin+soTVgmftiR8ZkQ==","signatures":[{"sig":"MEUCIHfCqZzsGvb1/UnAgaKzWWKX+/153qhmY7pyJAWDlQZEAiEAh2KFZE3nuXtYyhHAyEanZVc5mqXO0RQiCUIF0fO2nK4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":92234},"main":"src/index.js","engines":{"node":">=18.0.0"},"gitHead":"ffb0b213655f589f9a3021af24e3c55386d44af6","scripts":{"lint":"eslint src/","test":"jest","lint:fix":"eslint src/ --fix","test:watch":"jest --watch","test:coverage":"jest --coverage"},"_npmUser":{"name":"archana6","email":"accounts@appliqation.io"},"repository":{"url":"git+https://github.com/appliqation/playwright-pom-adapter.git","type":"git"},"_npmVersion":"11.7.0","description":"Converts Automan-generated Playwright scripts into a Page Object Model structure, without touching the Automan pipeline itself","directories":{},"_nodeVersion":"23.10.0","dependencies":{"@babel/parser":"^7.24.0","@babel/traverse":"^7.24.0","@appliqation/automation-sdk":"^2.7.0"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","eslint":"^9.9.0"},"_npmOperationalInternal":{"tmp":"tmp/playwright-pom-adapter_0.1.0_1787376231169_0.11282714309257824","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@appliqation/playwright-pom-adapter","version":"0.1.1","description":"Converts Automan-generated Playwright scripts into a Page Object Model structure, without touching the Automan pipeline itself","main":"src/index.js","bin":{"appq-pom-convert":"src/cli/convert.js","appq-pom-sync-back":"src/cli/syncBack.js"},"scripts":{"test":"jest","test:watch":"jest --watch","test:coverage":"jest --coverage","lint":"eslint src/","lint:fix":"eslint src/ --fix"},"keywords":["appliqation","testing","automation","playwright","page-object-model","codemod"],"author":{"name":"Appliqation Team"},"license":"MIT","dependencies":{"@appliqation/automation-sdk":"^2.7.0","@babel/parser":"^7.24.0","@babel/traverse":"^7.24.0"},"devDependencies":{"eslint":"^9.9.0","jest":"^29.7.0"},"engines":{"node":">=18.0.0"},"repository":{"type":"git","url":"git+https://github.com/appliqation/playwright-pom-adapter.git"},"bugs":{"url":"https://github.com/appliqation/playwright-pom-adapter/issues"},"homepage":"https://github.com/appliqation/playwright-pom-adapter#readme","gitHead":"9de36cd2c504a754a15e1d50dffb27c56a2a393d","_id":"@appliqation/playwright-pom-adapter@0.1.1","_nodeVersion":"23.10.0","_npmVersion":"11.7.0","dist":{"integrity":"sha512-FdO9GSLMxl4UBZo3MHs7Nrk1tXLwu2vDkyw4nNGgxmxp5kbOJaHLTxfUxNpVcfyjm52MwhWzfzUOGzgjBoEUeQ==","shasum":"750a334b5611ecf057cd4d77a9af2659b1fcbfde","tarball":"https://registry.npmjs.org/@appliqation/playwright-pom-adapter/-/playwright-pom-adapter-0.1.1.tgz","fileCount":17,"unpackedSize":90883,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIAOnzovJDJr1+fAthz5Yclrxt+2Iy6btTvdbLFK+n8tDAiEAuTt/nG2p1heK3QKmXYSUXLTOMEELpNhyxOOKqfdJGvQ="}]},"_npmUser":{"name":"archana6","email":"accounts@appliqation.io"},"directories":{},"maintainers":[{"name":"archana6","email":"accounts@appliqation.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/playwright-pom-adapter_0.1.1_1787378323718_0.33423391100060607"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-22T05:23:51.062Z","modified":"2026-08-22T05:58:44.001Z","0.1.0":"2026-08-22T05:23:51.385Z","0.1.1":"2026-08-22T05:58:43.866Z"},"bugs":{"url":"https://github.com/appliqation/playwright-pom-adapter/issues"},"author":{"name":"Appliqation Team"},"license":"MIT","homepage":"https://github.com/appliqation/playwright-pom-adapter#readme","keywords":["appliqation","testing","automation","playwright","page-object-model","codemod"],"repository":{"type":"git","url":"git+https://github.com/appliqation/playwright-pom-adapter.git"},"description":"Converts Automan-generated Playwright scripts into a Page Object Model structure, without touching the Automan pipeline itself","maintainers":[{"name":"archana6","email":"accounts@appliqation.io"}],"readme":"# @appliqation/playwright-pom-adapter\n\nConverts Automan-generated Playwright scripts into a Page Object Model structure,\nwithout changing Automan's own generation pipeline.\n\n## Why\n\nAppliqation's Automan generates a standalone Playwright script for every test\ncase, ready to run as-is. If your team works in a Page Object Model structure,\nthis package converts a set of those generated scripts into shared, reusable\npage classes that fit right into it. It's a separate, optional, dev-time tool —\nit reads the scripts already in your repo and writes a new POM structure\nalongside them, without touching the originals or calling any Appliqation API.\n\n## Installation\n\n```bash\nnpm install --save-dev @appliqation/playwright-pom-adapter\n```\n\nRequires Node.js >= 18. Depends on `@appliqation/automation-sdk` (for UUID\nvalidation) as a regular dependency — it installs automatically with the command\nabove, no separate step needed.\n\n## Configuration\n\nAuto-detection covers the common case, so there's usually nothing to configure.\nTwo settings are available if you want to override them, each resolvable via a\nCLI flag or an environment variable (the flag takes precedence):\n\n| Setting | CLI flag | Env var | Default |\n|---|---|---|---|\n| Where to read Automan's scripts from | `--source-dir` | `APPLIQATION_POM_SOURCE_DIR` | auto-detects `tests/appliqation`, then `tests/automan` |\n| Where to write the POM structure | `--output-dir` | `APPLIQATION_POM_OUTPUT_DIR` | `tests/POM` |\n\nConfiguration is CLI-flag and environment-variable only — the same convention\n`@appliqation/automation-sdk`'s own CLI (`appq-auth-setup`) uses, with no\nseparate config file to manage.\n\n**The manifest.** Each run writes `.appliqation-pom-manifest.json` at the project\nroot — a content-hash index of every source script processed so far, which is\nwhat makes re-runs incremental (see Usage below). Commit this file alongside the\ngenerated output. If it's ever missing, the next run treats every source script\nas new and does a full regeneration — correct, just a little slower.\n\n## Usage\n\n```bash\nnpx appq-pom-convert --source-dir tests/automan --output-dir tests/POM\n```\n\nRun it whenever you've pulled new or updated Automan-generated scripts — think\nof it as a build step rather than a watcher. To automate it, wire the command\ninto whatever already runs after an Automan PR merges (a post-merge hook, a CI\nstep, or just a habit).\n\n**Typical workflow:**\n1. Automan's GitHub bot opens a PR with new/updated scripts under `tests/automan/`.\n2. Merge it, same as today.\n3. Run `npx appq-pom-convert`.\n4. Commit the result — the updated `tests/POM/` directory and the updated\n   `.appliqation-pom-manifest.json`.\n\n**As an npm script** (add to your `package.json`):\n```json\n{\n  \"scripts\": {\n    \"pom:convert\": \"appq-pom-convert --source-dir tests/automan --output-dir tests/POM\"\n  }\n}\n```\n\n**Programmatic API**, for calling it from your own script instead of the CLI:\n```js\nconst { convert } = require('@appliqation/playwright-pom-adapter');\n\nconst result = convert({\n  cwd: process.cwd(),\n  sourceDir: 'tests/automan',   // optional — auto-detects if omitted\n  outputDir: 'tests/POM',       // optional — defaults to tests/POM\n  log: (msg) => console.log(msg), // optional — defaults to a no-op\n});\n\n// result: { pagesWritten: string[], specsWritten: string[], skipped: Array<{filePath, reason}> }\n```\n\n**Running the converted tests** — nothing changes about how you run Playwright.\nThe generated specs under `tests/POM/specs/` are ordinary `@playwright/test`\nfiles; `npx playwright test tests/POM` runs them exactly like any other spec\ndirectory. Results report back to Appliqation identically (see \"What it\npreserves\" below), so this runs safely in the same CI job you already have.\n\n## Reverse sync: propagating a selector fix back to Automan\n\nWhen a selector breaks, the fastest fix is often to make it directly in the\ngenerated Page Object method — `appq-pom-sync-back` takes that fix and applies\nit back to every original Automan script it came from:\n\n```bash\nnpx appq-pom-sync-back --source-dir tests/automan --pom-dir tests/POM\n```\n\nIt defaults to a **dry run**: it prints every detected selector change, which\noriginal file(s) it would update, and any conflicts, without writing anything.\nPass `--apply` to write the changes into `--source-dir`. This is the only\ncommand in the package that writes to Automan's source directory, so — unlike\n`appq-pom-convert` — it always requires that explicit opt-in.\n\n**How it works:** it re-derives the expected Page Object content directly from\nthe current `tests/automan/`, compares that against what's on disk in\n`tests/POM/pages/`, and for every method whose selector differs, applies that\nexact change to every original file that contributed to it — a method shared by\n20 scripts updates all 20. Before touching any file, it confirms that file's\ncontent matches what the currently-displayed POM was generated from. Automan's\nown bot can update `tests/automan/` independently at any time (self-healing);\nif a file has moved on since, it's reported as a conflict and left untouched —\nre-run `appq-pom-convert` to see the current state, then run sync-back again.\n\n**Scope: what this keeps in Automan's hands.** Selector fixes are the only edit\nthat flows back. A brand-new Page Object method, a renamed one, or a change to\na spec's flow all stay exactly where you made them — there's no original\nAutoman action for a hand-written method to map onto, so propagating those\nwould mean generating a new Automan script from POM code, which is a different\ncapability from reverse sync. The one case it also leaves for you to resolve\ndirectly: a variable refined further at its call site (e.g.\n`docRow.getByRole('button', {...}).click()`, where the full selector spans both\nthe variable's declaration and the extra chaining at the call) — reported\nclearly rather than guessed at.\n\n**Adding a genuinely new interaction to the POM structure** works the same way\nit always has, with no reverse sync involved: edit `tests/automan/` directly\n(in the same style Automan writes) and run `appq-pom-convert`. It folds the new\naction into an existing Page Object method when the selector matches, or\ncreates a new one when it doesn't. In short: **add new interactions in the\nsource, fix selectors in the generated POM.**\n\n**Programmatic API:**\n```js\nconst { syncBack } = require('@appliqation/playwright-pom-adapter');\n\nconst result = syncBack({\n  cwd: process.cwd(),\n  sourceDir: 'tests/automan', // optional — auto-detects if omitted\n  pomDir: 'tests/POM',        // optional — defaults to tests/POM\n  apply: false,               // optional — defaults to a dry run, same as the CLI\n  log: (msg) => console.log(msg), // optional — defaults to a no-op\n});\n\n// result: {\n//   applied: Array<{ className, methodName, playwrightMethod, oldSelectorRaw, newSelectorRaw, sources, filePath }>,\n//   conflicts: Array<{ ...same shape, filePath }>,             // source changed since generation\n//   unsupported: Array<{ ...same shape, filePath, reason }>,   // needs manual resolution\n// }\n```\n\n## What it preserves\n\nEvery `[AUTOMAN — DO NOT REMOVE]` boilerplate block — the SDK import, `setupAuth`\nsession wiring, credentials declaration, and the `mapAppqUuid(testInfo, uuid)` call\n— carries into the generated spec file unchanged. Test results report back to\nAppliqation identically whether a test runs from `tests/automan/` or the generated\nPOM structure, because that mapping is a Playwright test annotation at runtime,\nnot a function of file location.\n\n## Troubleshooting\n\n**\"Skipping `<file>`: expected exactly one test(...) call, found N\" /\n\"uses a test.describe() wrapper — pre-contract legacy shape, not supported\"**\nThese are scripts written before Automan's current script contract, or output\nfrom an unrelated export path. The adapter skips them and explains why rather\nthan guessing at a fix — the file stays out of the generated POM structure and\nkeeps running unchanged under `tests/automan/`.\n\n**\"mapAppqUuid(testInfo, ...) is called but testInfo is not declared...\"**\nA small number of older generated scripts predate a fix in Automan and are\nmissing the `testInfo` parameter on the test callback, which throws at runtime\nindependent of this tool. Skipped for the same reason as above; regenerating\nthat test case's script through Automan resolves it.\n\n**A page/method I expected to be clustered is missing, and the original\ninteraction is still sitting inline in the generated spec instead.**\nOne of two deliberate safety checks:\n- The variable is referenced somewhere other than a recognized action call\n  (e.g. `expect(emailField).toHaveValue(...)` after filling it) — folding it\n  into a shared Page Object method would leave that assertion pointing at a\n  variable that no longer exists.\n- The locator's arguments embed a variable from the script's own local scope\n  (e.g. `page.getByRole('button', { name: someLocalVar })`) — hoisting that\n  into a class method would reference a variable the class doesn't have.\n\nIn both cases, the interaction is preserved exactly as Automan generated it, in\nits own file, rather than producing output that would fail at runtime.\n\n**Running it again does nothing / \"Up to date — no source scripts changed\".**\nExpected — the manifest found no content changes since the last run. To force a\nfull regeneration (for example, after upgrading to a version with different\nclustering behavior), delete `.appliqation-pom-manifest.json` and re-run.\n\n**A generated Page Object method has a generic name (`clickElement`,\n`fillTextbox2`).**\nNaming prefers the original script's own variable name (`emailField` →\n`fillEmailField`) when one exists. For an inline action with no variable, it\nfalls back to the selector's accessible name, role, or id, and to a generic\n`Element` placeholder when the selector carries no identifying text at all\n(a bare `page.getByRole('textbox')`, or a `name:` built from a variable rather\nthan a string literal). Clustering identity is always based on the selector\ntext, not the name, so this never affects correctness — rename the method\ndirectly in the generated file if you'd like a better name; it's recomputed\nthe next time that Page Object's content changes.\n\n**Module resolution errors on the generated spec files\n(`Cannot find module '../../pages/HomePage'`, etc.).**\nThe generated specs use relative `require()` paths computed from your\n`--output-dir`. If you move the `tests/POM/` directory after generating it,\nre-run the converter rather than moving files by hand. If your project's\nTypeScript config is strict about interop, set `esModuleInterop: true` — the\ngenerated files mix `import`/`require()` the same way Automan's own scripts do,\nwhich `@playwright/test`'s built-in transpilation already handles without any\nextra configuration.\n\n**`appq-pom-sync-back` reports a conflict for a file.**\nThe Automan source file has changed since the POM you're looking at was\ngenerated — most often because Automan's own bot self-healed that script\nindependently. Re-run `appq-pom-convert` to bring the POM up to date, reconcile\nif needed, then reapply your fix and run sync-back again.\n\n**`appq-pom-sync-back` reports \"refined further at its call site... resolve\nmanually\" or \"N ambiguous matches.\"**\nThe same principle as the forward converter's safety checks applies: rather\nthan guess, the tool tells you exactly what to look at. \"Refined further\" means\nthe element's selector spans both a variable's declaration and extra chaining\nat the call site — edit the original file(s) directly for that one. \"Ambiguous\nmatches\" means it found more than one candidate location in a file and is\nleaving the choice to you.\n\n**Does this support &lt;framework X&gt; instead of / in addition to classic POM?**\nClassic Page Object Model is what ships today. Fixture-based POM (Playwright's\nown `test.extend()` style) and Component Object Model are designed as\nadditional renderers over the same clustering engine — see Scope below.\n\n## Scope\n\n- **Greenfield output.** This tool defines its own Page Object structure and\n  naming conventions for the directory it generates. Matching an existing,\n  hand-authored POM suite's own conventions is a distinct problem with its own\n  solution, and isn't what this tool is for.\n- **Deterministic clustering, no AI.** Elements are deduplicated by exact\n  selector-text identity across every script in one run (formatting\n  differences like line-wrapping are normalized away, so the same chain\n  written two different ways still dedupes); pages are named from the\n  `page.goto()` URL active when an element was first seen. On a single-page\n  app that navigates client-side after one `goto()`, this still dedupes\n  methods correctly but groups them under one page class rather than one per\n  route — a Component Object Model renderer, on the roadmap, is designed for\n  that case specifically, as a different clustering strategy rather than a\n  change to this one.\n- **Forward conversion owns its output; reverse sync is the one exception.**\n  `appq-pom-convert` recomputes the full cluster every run and only rewrites\n  files whose computed content changed — it doesn't merge hand-edits itself.\n  `appq-pom-sync-back` is the path for exactly one kind of edit (a changed\n  selector) — see Reverse sync above. New methods, renamed methods, and\n  spec-flow changes are regenerated by the next forward `convert`, by design.\n  If a source script is removed, its contributed methods are left in place\n  and reported as a warning rather than deleted automatically.\n- Off-contract or legacy-shaped scripts, and any locator whose definition\n  depends on a script-local variable, are skipped or left inline rather than\n  guessed at — see Troubleshooting above for both.\n- Classic Page Object Model is the renderer available today. Fixture-based POM\n  (Playwright's own `test.extend()` style) and Component Object Model are\n  designed as additional renderers over the same clustered intermediate\n  representation, and are next on the roadmap.\n\n## Architecture\n\n```\nsrc/\n  parser/\n    contractGrammar.js   # regex constants ported from Automan's ScriptContract\n    discoverScripts.js   # filesystem walk for scenario-<id>/<uuid>.spec.ts\n    parseScript.js        # contract validation + AST-based extraction\n    irUtils.js             # flattenIR/transformIR — walk the container (if/try/for/while) IR tree\n  cluster/\n    clusterActions.js     # cross-script selector dedup, page bucketing\n  render/\n    pomTemplate.js         # string-template codegen (not AST-print)\n  cache/\n    manifest.js             # content-hash based incremental regeneration\n  reverseSync/\n    parsePageObjectFile.js  # extracts each method's current selector from a generated Page Object file\n    detectChanges.js         # diffs that against a freshly-recomputed (expected) cluster\n    patchSourceFile.js        # locates and applies the old->new selector to one original automan file\n  cli/\n    convert.js               # CLI entry point — appq-pom-convert\n    syncBack.js               # CLI entry point — appq-pom-sync-back\n  index.js                    # programmatic API: convert({...}), syncBack({...})\n  syncBack.js                 # syncBack() orchestration (discover -> parse -> cluster -> detect -> patch)\n```\n","readmeFilename":"README.md"}