{"id":13651445,"url":"https://github.com/DyHex/POMWright","last_synced_at":"2025-04-22T22:31:18.645Z","repository":{"id":216502064,"uuid":"729889603","full_name":"DyHex/POMWright","owner":"DyHex","description":"POMWright is a complementary test framework for Playwright written in TypeScript.","archived":false,"fork":false,"pushed_at":"2025-03-11T12:33:21.000Z","size":2930,"stargazers_count":41,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-14T06:06:49.873Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DyHex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-10T17:04:17.000Z","updated_at":"2025-01-31T08:12:29.000Z","dependencies_parsed_at":"2024-01-29T09:17:07.120Z","dependency_job_id":"9bce23ea-bd2c-4421-bcf5-7cfa7dcd18fc","html_url":"https://github.com/DyHex/POMWright","commit_stats":{"total_commits":87,"total_committers":5,"mean_commits":17.4,"dds":0.4482758620689655,"last_synced_commit":"f86febdfde29180ceb6f7377e31d62d51ed5b037"},"previous_names":["dyhex/pomwright"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DyHex%2FPOMWright","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DyHex%2FPOMWright/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DyHex%2FPOMWright/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DyHex%2FPOMWright/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DyHex","download_url":"https://codeload.github.com/DyHex/POMWright/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250333940,"owners_count":21413480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T02:00:49.540Z","updated_at":"2025-04-22T22:31:18.638Z","avatar_url":"https://github.com/DyHex.png","language":"TypeScript","funding_links":[],"categories":["Utils"],"sub_categories":[],"readme":"# POMWright\n\n![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/DyHex/POMWright/main.yaml?label=CI%20on%20main) [![NPM Version](https://img.shields.io/npm/v/pomwright)](https://www.npmjs.com/package/pomwright) ![NPM Downloads](https://img.shields.io/npm/dt/pomwright) ![GitHub License](https://img.shields.io/github/license/DyHex/POMWright) [![NPM dev or peer Dependency Version](https://img.shields.io/npm/dependency-version/pomwright/peer/%40playwright%2Ftest)](https://www.npmjs.com/package/playwright) [![Static Badge](https://img.shields.io/badge/created%40-ICE-ffcd00)](https://www.ice.no/)\n\nPOMWright is a TypeScript-based framework that implements the Page Object Model Design Pattern, designed specifically to augment Playwright's testing capabilities.\n\nPOMWright provides a way of abstracting the implementation details of a web page and encapsulating them into a reusable page object. This approach makes the tests easier to read, write, and maintain, and helps reduce duplicated code by breaking down the code into smaller, reusable components, making the code more maintainable and organized.\n\n## Features\n\n### Easy Creation of Page Object Classes\n\nSimply extend a class with `BasePage` to create a Page Object Class (POC).\n\n### Support for Multiple Domains/BaseURLs\n\nDefine different base URLs by extending an abstract class with `BasePage` per domain and have your POCs for each domain extend the abstract classes.\n\n### Custom Playwright Fixture Integration\n\nSeamlessly integrate custom Playwright Fixtures with your POMWright POCs.\n\n### LocatorSchema Interface\n\nDefine comprehensive locators for each POC and share common locators between them.\n\n### Advanced Locator Management\n\nEfficiently manage and chain locators through `LocatorSchemaPath`s.\n\n### Dynamic Locator Schema Updates\n\nModify single or multiple locators within a chained locator dynamically during tests using the new `.update()` and `.addFilter()` methods.\n\n### Deep Copy of LocatorSchemas\n\nEnsure that original `LocatorSchemas` remain immutable and reusable across tests.\n\n### Custom HTML Logger\n\nGain insights with detailed logs for nested locators, integrated with Playwright's HTML report. Or use the Log fixture throughout your own POCs and tests to easily attach them to the HTML report based on log levels.\n\n### SessionStorage Handling\n\nEnhance your tests with advanced `sessionStorage` handling capabilities.\n\n### Enhanced Locator Filtering\n\n- **New `filter` Property**: Apply filters across various locator types beyond just the `locator` method.\n- **New `.addFilter()` Method**: Dynamically add filters to any part of the locator chain.\n\n## Installation\n\nEnsure you have Node.js installed, then run:\n\n```bash\nnpm install pomwright --save-dev\n```\n\nor\n\n```bash\npnpm i -D pomwright\n```\n\n## Playwright Example Project\n\nExplore POMWright in action by diving into the example project located in the \"example\" folder. Follow these steps to get started:\n\n### Install\n\nNavigate to the \"example\" folder and install the necessary dependencies:\n\n```bash\npnpm install\n```\n\n### Playwright Browsers\n\nInstall or update Playwright browsers:\n\n```bash\npnpm playwright install --with-deps\n```\n\n### Run Tests\n\nExecute tests across Chromium, Firefox, and WebKit:\n\n```bash\npnpm playwright test\n```\n\n### Parallelism\n\nControl parallel test execution. By default, up to 4 tests run in parallel. Modify this setting as needed:\n\n```bash\npnpm playwright test --workers 2  # Set the number of parallel workers\n```\n\n### Reports\n\nAfter the tests complete, a Playwright HTML report is available in `./example/playwright-report`. Open the `index.html` file in your browser to view the results.\n\n## Usage\n\nDive into using POMWright with these examples:\n\n### Create a Page Object Class (POC)\n\n```typescript\nimport { Page, TestInfo } from \"@playwright/test\";\nimport { BasePage, PlaywrightReportLogger, GetByMethod } from \"pomwright\";\n\ntype LocatorSchemaPath = \n  | \"content\"\n  | \"content.heading\"\n  | \"content.region.details\"\n  | \"content.region.details.button.edit\";\n\nexport default class Profile extends BasePage\u003cLocatorSchemaPath\u003e {\n  constructor(page: Page, testInfo: TestInfo, pwrl: PlaywrightReportLogger) {\n    super(page, testInfo, \"https://someDomain.com\", \"/profile\", Profile.name, pwrl);\n  }\n\n  protected initLocatorSchemas() {\n    this.locators.addSchema(\"content\", {\n      locator: \".main-content\",\n      locatorMethod: GetByMethod.locator\n    });\n\n    this.locators.addSchema(\"content.heading\", {\n      role: \"heading\",\n      roleOptions: {\n        name: \"Your Profile\"\n      },\n      locatorMethod: GetByMethod.role\n    });\n\n    this.locators.addSchema(\"content.region.details\", {\n      role: \"region\",\n      roleOptions: {\n        name: \"Profile Details\"\n      },\n      locatorMethod: GetByMethod.role\n    });\n\n    this.locators.addSchema(\"content.region.details.button.edit\", {\n      role: \"button\",\n      roleOptions: {\n        name: \"Edit\"\n      },\n      locatorMethod: GetByMethod.role\n    });\n  }\n\n  // add your helper methods here...\n}\n```\n\n### Creating a Custom Playwright Fixture\n\n```typescript\nimport { test as base } from \"pomwright\";\nimport Profile from \"...\";\n\ntype fixtures = {\n  profile: Profile;\n}\n\nexport const test = base.extend\u003cfixtures\u003e({\n  profile: async ({ page, log }, use, testInfo) =\u003e {\n    const profile = new Profile(page, testInfo, log);\n    await use(profile);\n  }\n});\n```\n\n### Using the Fixture in Playwright Tests\n\n#### Click Edit Button with a Single Locator\n\n```typescript\nimport { test } from \".../fixtures\";\n\ntest(\"click edit button with a single locator\", async ({ profile }) =\u003e {\n  // perform setup/navigation...\n\n  await profile.page.waitForURL(profile.fullUrl);\n\n  /** \n   * returns the locator resolving to the full locatorSchemaPath\n   */\n  const editBtn = await profile.getLocator(\"content.region.details.button.edit\");\n\n  await editBtn.click();\n});\n```\n\n#### Click Edit Button with a Nested Locator\n\n```typescript\nimport { test } from \".../fixtures\";\n\ntest(\"click edit button with a nested locator\", async ({ profile }) =\u003e {\n  // perform setup/navigation...\n\n  await profile.page.waitForURL(profile.fullUrl);\n\n  /** \n   * returns a nested/chained locator consisting of the 3 locators: \n   * content, \n   * content.region.details\n   * content.region.details.button.edit\n   */\n  const editBtn = await profile.getNestedLocator(\"content.region.details.button.edit\");\n\n  await editBtn.click();\n});\n```\n\n#### Specify Index for Nested Locators\n\n```typescript\nimport { test } from \".../fixtures\";\n\ntest(\"specify index for nested locator(s)\", async ({ profile }) =\u003e {\n  // perform setup/navigation...\n\n  await profile.page.waitForURL(profile.fullUrl);\n\n  /** \n   * returns a nested/chained locator consisting of the 3 locators: \n   * content \n   * content.region.details with .first()\n   * content.region.details.button.edit with .nth(1)\n   */\n  const editBtn = await profile.getNestedLocator(\"content.region.details.button.edit\", {\n    \"content.region.details\": 0, \n    \"content.region.details.button.edit\": 1\n  });\n\n  await editBtn.click();\n});\n```\n\n#### Update a Locator Before Use\n\n```typescript\nimport { test } from \".../fixtures\";\n\ntest(\"update a locator before use\", async ({ profile }) =\u003e {\n  // perform setup/navigation...\n\n  await profile.page.waitForURL(profile.fullUrl);\n\n  /** \n   * returns a nested/chained locator consisting of the 3 locators: \n   * content, \n   * content.region.details\n   * content.region.details.button.edit (updated)\n   */\n  const editBtn = await profile.getLocatorSchema(\"content.region.details.button.edit\")\n    .update(\"content.region.details.button.edit\", { \n      roleOptions: { name: \"Edit details\" }\n    })\n    .getNestedLocator();\n\n  await editBtn.click();\n});\n```\n\n#### Update a Nested Locator Before Use\n\n```typescript\nimport { test } from \".../fixtures\";\n\ntest(\"update a nested locator before use\", async ({ profile }) =\u003e {\n  // perform setup/navigation...\n\n  await profile.page.waitForURL(profile.fullUrl);\n\n  /** \n   * returns a nested/chained locator consisting of the 3 locators: \n   * content, \n   * content.region.details (updated)\n   * content.region.details.button.edit\n   */\n  const editBtn = await profile.getLocatorSchema(\"content.region.details.button.edit\")\n    .update(\"content.region.details\", { \n      locator: \".profile-details\",\n      locatorMethod: GetByMethod.locator\n    })\n    .getNestedLocator();\n\n  await editBtn.click();\n});\n```\n\n#### Make Multiple Versions of a Locator\n\n```typescript\nimport { test } from \".../fixtures\";\n\ntest(\"make multiple versions of a locator\", async ({ profile }) =\u003e {\n  // perform setup/navigation...\n\n  await profile.page.waitForURL(profile.fullUrl);\n\n  const editBtnSchema = profile.getLocatorSchema(\"content.region.details.button.edit\");\n\n  const editBtn = await editBtnSchema.getLocator();\n  await editBtn.click();\n\n  editBtnSchema.update(\"content.region.details.button.edit\", { roleOptions: { name: \"Edit details\" } });\n\n  const editBtnUpdated = await editBtnSchema.getNestedLocator();\n  await editBtnUpdated.click();\n\n  /**\n   * Calling profile.getLocatorSchema(\"content.region.details.button.edit\") again \n   * will return a new deepCopy of the original LocatorSchema\n   */\n});\n```\n\n## Deprecations\n\n### 1. Deprecated `.update` and `.updates` Methods\n\n- **Old `.update(updates: Partial\u003cLocatorSchemaWithoutPath\u003e): LocatorSchemaWithMethods`**\n- **Old `.updates(indexedUpdates: { [index: number]: Partial\u003cLocatorSchemaWithoutPath\u003e | null }): LocatorSchemaWithMethods`**\n\n**Reason:**  \nThe `.updates` method relied on index-based updates, which are prone to errors and require manual maintenance, especially when `LocatorSchemaPath` strings are renamed or restructured. Additionally, the old `.update` method could only update the last `LocatorSchema` in the chain, making it less flexible.\n\n**Replacement:**  \nUse the new `.update(subPath, modifiedSchema)` method, which leverages valid `subPath`s of `LocatorSchemaPath` strings for more intuitive and maintainable updates.\n\n**Removal Schedule:**  \nThese methods are deprecated and will be removed in version 2.0.0.\n\n### 2. Deprecated Old `getNestedLocator` Method\n\n- **Old `getNestedLocator(indices?: { [key: number]: number | null }): Promise\u003cLocator\u003e`**\n\n**Reason:**  \nIndex-based indexing is less readable and requires manual updates when `LocatorSchemaPath` strings change.\n\n**Replacement:**  \nUse the updated `getNestedLocator(subPathIndices?: { [K in SubPaths\u003cLocatorSchemaPathType, LocatorSubstring\u003e]: number | null }): Promise\u003cLocator\u003e` method, which utilizes `LocatorSchemaPath` strings for indexing.\n\n**Removal Schedule:**  \nThis method is deprecated and will be removed in version 2.0.0.\n\n## Migration Guide\n\n### Updating `.update` and `.updates` Methods\n\n**Old Usage:**\n```typescript\nconst allCheckboxes = await poc\n  .getLocatorSchema(\"main.products.searchControls.filterType.label.checkbox\")\n  .updates({ 3: { locatorOptions: { hasText: /Producer/i } } })\n  .getNestedLocator();\n```\n\n**New Usage:**\n```typescript\nconst allCheckboxes = await poc\n  .getLocatorSchema(\"main.products.searchControls.filterType.label.checkbox\")\n  .update(\"main.products.searchControls.filterType\", { locatorOptions: { hasText: /Producer/i } })\n  .getNestedLocator();\n```\n\n### Updating `getNestedLocator` Method\n\n**Old Usage (Deprecated):**\n```typescript\nconst saveBtn = await profile.getNestedLocator(\"content.region.details.button.save\", { 4: 2 });\n\nconst editBtn = await profile.getLocatorSchema(\"content.region.details.button.edit\")\n  .getNestedLocator({ 2: index });\n```\n\n**New Usage:**\n```typescript\nconst saveBtn = await profile.getNestedLocator(\"content.region.details.button.save\", {\n  \"content.region.details.button.save\": 2 \n});\n\nconst editBtn = await profile.getLocatorSchema(\"content.region.details.button.edit\")\n  .getNestedLocator({ \"content.region.details\": index });\n```\n\n### Utilizing `LocatorSchemaPath` Instead of Indices\n\nTransition from index-based to `LocatorSchemaPath`-based indexing to improve code readability and maintainability.\n\n**Old Example:**\n```typescript\nconst allCheckboxes = await poc\n  .getLocatorSchema(\"main.form.item.checkbox\")\n  .updates({ 3: { locatorOptions: { hasText: /Producer/i } } })\n  .getNestedLocator();\n```\n\n**New Example:**\n```typescript\nconst allCheckboxes = await poc\n  .getLocatorSchema(\"main.form.item.checkbox\")\n  .update(\"main.form.item\", { locatorOptions: { hasText: /Producer/i } })\n  .getNestedLocator();\n```\n\n## Example in Context\n\n### Defining a LocatorSchema with `filter` and Using `.addFilter()`\n\n```typescript\n// Defining LocatorSchemas\nthis.locators.addSchema(\"body.main.section@userInfo\", {\n  role: \"region\",\n  roleOptions: { name: \"Contact Info\" },\n  filter: { hasText: /e-mail/i },\n  locatorMethod: GetByMethod.role\n});\n\n// Dynamically adding additional filters using `.addFilter()`\nconst specificSection = await poc\n  .getLocatorSchema(\"body.main.section@userInfo\")\n  .addFilter(\"body.main.section@userInfo\", { hasText: \"Additional Services\" })\n  .getNestedLocator();\n```\n\n### Updating LocatorSchemas with the New `.update()` Method\n\n```typescript\nconst editBtn = await profile\n  .getLocatorSchema(\"content.region.details.button.edit\")\n  .update(\"content.region.details.button.edit\", { \n    roleOptions: { name: \"new accessibility name\" }\n  })\n  .getNestedLocator();\n```\n\n### Reusing LocatorSchemas with `LocatorSchemaWithoutPath`\n\n```typescript\nimport { GetByMethod, LocatorSchemaWithoutPath } from \"pomwright\";\nimport { missingInputError } from \"@common/page-components/errors.locatorSchema.ts\";\n\nexport type LocatorSchemaPath =\n  | \"body\"\n  | \"body.main\"\n  | \"body.main.section\"\n  | \"body.main.section@products\"\n  | \"body.main.section@userInfo\"\n  | \"body.main.section@userInfo.input@email\"\n  | \"body.main.section@userInfo.inputError\"\n  | \"body.main.section@deliveryInfo\";\n\nexport function initLocatorSchemas(locators: GetLocatorBase\u003cLocatorSchemaPath\u003e) {\n  locators.addSchema(\"body\", {\n    locator: \"body\",\n    locatorMethod: GetByMethod.locator,\n  });\n\n  locators.addSchema(\"body.main\", {\n    locator: \"main\",\n    locatorMethod: GetByMethod.locator,\n  });\n\n  const region: LocatorSchemaWithoutPath = { role: \"region\", locatorMethod: GetByMethod.role };\n\n  locators.addSchema(\"body.main.section\", {\n    ...region,\n  });\n\n  locators.addSchema(\"body.main.section@products\", {\n    ...region,\n    roleOptions: { name: \"Products\" },\n  });\n\n  locators.addSchema(\"body.main.section@userInfo\", {\n    ...region,\n    roleOptions: { name: \"Contact Info\" },\n    filter: { hasText: /e-mail/i },\n  });\n\n  locators.addSchema(\"body.main.section@userInfo.input@email\", {\n    role: \"textbox\",\n    roleOptions: { name: \"Input your e-mail\" },\n    locatorMethod: GetByMethod.role,\n  });\n\n  locators.addSchema(\"body.main.section@userInfo.inputError\", {\n    ...missingInputError,\n  });\n\n  locators.addSchema(\"body.main.section@deliveryInfo\", {\n    ...region,\n    roleOptions: { name: \"Delivery Info\" },\n  });\n}\n```\n\n## Troubleshooting and Support\n\nIf you encounter any issues or have questions, please check our [issues page](https://github.com/DyHex/POMWright/issues) or reach out to us directly.\n\n## Contributing\n\nPull Requests are welcome! Please open an issue or submit a pull request for any enhancements or bug fixes.\n\n## License\n\nPOMWright is open-source software licensed under the [Apache-2.0 license](https://github.com/DyHex/POMWright/blob/main/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDyHex%2FPOMWright","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDyHex%2FPOMWright","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDyHex%2FPOMWright/lists"}