pomwright 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +1 -1
  3. package/biome.json +9 -0
  4. package/dist/index.d.mts +2 -1
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.js +61 -23
  7. package/dist/index.mjs +61 -23
  8. package/index.ts +1 -1
  9. package/pack-test.sh +47 -0
  10. package/package.json +9 -5
  11. package/vitest.config.ts +9 -0
  12. package/.vscode/extensions.json +0 -5
  13. package/.vscode/playwright-snippets.code-snippets +0 -82
  14. package/.vscode/settings.json +0 -4
  15. package/example/.env +0 -1
  16. package/example/fixtures/all.fixtures.ts +0 -4
  17. package/example/package.json +0 -18
  18. package/example/page-object-models/anotherDomain/.gitkeep +0 -0
  19. package/example/page-object-models/saucedemo/common/base-page/saucedemo.page.ts +0 -28
  20. package/example/page-object-models/saucedemo/common/page-components/common.locatorScema.ts +0 -11
  21. package/example/page-object-models/saucedemo/common/page-components/headerMenu/headerMenu.locatorSchema.ts +0 -45
  22. package/example/page-object-models/saucedemo/common/pages/.gitkeep +0 -0
  23. package/example/page-object-models/saucedemo/common/utils/.gitkeep +0 -0
  24. package/example/page-object-models/saucedemo/fixtures/saucedemo.fixtures.ts +0 -20
  25. package/example/page-object-models/saucedemo/pages/home.locatorSchema.ts +0 -99
  26. package/example/page-object-models/saucedemo/pages/home.page.ts +0 -29
  27. package/example/page-object-models/saucedemo/pages/inventory/inventory.locatorSchema.ts +0 -22
  28. package/example/page-object-models/saucedemo/pages/inventory/inventory.page.ts +0 -26
  29. package/example/playwright.config.ts +0 -63
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # pomwright
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#23](https://github.com/DyHex/POMWright/pull/23) [`0cfc19f`](https://github.com/DyHex/POMWright/commit/0cfc19f057575365853f9df41bbd661bf45172e2) Thanks [@DyHex](https://github.com/DyHex)! - # Continuous testing & bug fixes
8
+
9
+ ## Bug fixes
10
+
11
+ - getLocatorBase.applyUpdate() now correctly updates the LocatorSchemaWithMethod and maintains a circular ref. for the entry representing itself in schemasMap
12
+ - getLocatorBase.applyUpdates() now correctly updates the LocatorSchemaWithMethod and maintains a circular ref. for the entry representing itself in schemasMap while updating other LocatorSchema in SchemasMap directly.
13
+ - getLocatorBase.deepMerge() now correctly validates valid nested properties of LocatorSchema
14
+
15
+ ## Continuous testing
16
+
17
+ - Build workflow now runs unit tests (vitest)
18
+ - New shell script enabling testing new packages before release
19
+ - New test workflow for POMWright integration tests (Playwright/test)
20
+ - 52 new unit tests, more to come..
21
+ - 4 new integration tests, more to come..
22
+
23
+ New release has also been tested with a seperate Playwright/test project leveraging POMWright (~100 E2E tests)
24
+
25
+ ## Playwright/test compatibility
26
+
27
+ Tested with the following Playwright/test versions:
28
+
29
+ - 1.43.1
30
+ - 1.43.0
31
+ - 1.42.1
32
+ - 1.42.0 (not recommended)
33
+ - 1.41.2
34
+ - 1.41.1
35
+ - 1.41.0
36
+ - 1.40.1
37
+ - 1.40.0
38
+ - 1.39.0
39
+
3
40
  ## 1.0.0
4
41
 
5
42
  ### Major Changes
package/README.md CHANGED
@@ -14,7 +14,7 @@ Simply extend a class with BasePage to create a Page Object Class (POC).
14
14
 
15
15
  ### Support for Multiple Domains/BaseURLs
16
16
 
17
- Define different base URLs by extending an abstract class with BasePage and have your POCs extend it.
17
+ Define different base URLs by extending an abstract class with BasePage per domain and have your POCs for each domain extend the abstract classes.
18
18
 
19
19
  ### Custom Playwright Fixture Integration
20
20
 
package/biome.json CHANGED
@@ -16,5 +16,14 @@
16
16
  "indentWidth": 2,
17
17
  "lineWidth": 120,
18
18
  "ignore": []
19
+ },
20
+ "files": {
21
+ "ignore": [
22
+ "dist/**",
23
+ "node_modules/**",
24
+ "example/node_modules/**",
25
+ "example/playwright-report/**",
26
+ "example/test-results/**"
27
+ ]
19
28
  }
20
29
  }
package/dist/index.d.mts CHANGED
@@ -104,7 +104,7 @@ interface LocatorSchema {
104
104
  /** Defines the preferred Playwright locator method to be used on this LocatorSchema Object */
105
105
  locatorMethod: GetByMethod;
106
106
  /** The human-readable name of the defined locator object, used for debug logging and test report enrichment. */
107
- locatorSchemaPath: string;
107
+ readonly locatorSchemaPath: string;
108
108
  }
109
109
 
110
110
  type LogLevel = "debug" | "info" | "warn" | "error";
@@ -249,6 +249,7 @@ declare class GetLocatorBase<LocatorSchemaPathType extends string> {
249
249
  * It ensures that each locator schema and its sub-schemas are properly cloned and stored.
250
250
  */
251
251
  private collectDeepCopies;
252
+ private isLocatorSchemaWithMethods;
252
253
  /**
253
254
  * Applies an update to a specific locator schema within the provided map of schemas.
254
255
  * This method ensures that the specified updates are merged into the targeted locator schema.
package/dist/index.d.ts CHANGED
@@ -104,7 +104,7 @@ interface LocatorSchema {
104
104
  /** Defines the preferred Playwright locator method to be used on this LocatorSchema Object */
105
105
  locatorMethod: GetByMethod;
106
106
  /** The human-readable name of the defined locator object, used for debug logging and test report enrichment. */
107
- locatorSchemaPath: string;
107
+ readonly locatorSchemaPath: string;
108
108
  }
109
109
 
110
110
  type LogLevel = "debug" | "info" | "warn" | "error";
@@ -249,6 +249,7 @@ declare class GetLocatorBase<LocatorSchemaPathType extends string> {
249
249
  * It ensures that each locator schema and its sub-schemas are properly cloned and stored.
250
250
  */
251
251
  private collectDeepCopies;
252
+ private isLocatorSchemaWithMethods;
252
253
  /**
253
254
  * Applies an update to a specific locator schema within the provided map of schemas.
254
255
  * This method ensures that the specified updates are merged into the targeted locator schema.
package/dist/index.js CHANGED
@@ -407,6 +407,15 @@ var GetBy = class {
407
407
  };
408
408
 
409
409
  // src/helpers/getLocatorBase.ts
410
+ var REQUIRED_PROPERTIES_FOR_LOCATOR_SCHEMA_WITH_METHODS = [
411
+ "update",
412
+ "updates",
413
+ "getNestedLocator",
414
+ "getLocator",
415
+ "locatorSchemaPath",
416
+ "locatorMethod",
417
+ "schemasMap"
418
+ ];
410
419
  var GetLocatorBase = class {
411
420
  /**
412
421
  * Initializes the GetLocatorBase class with a page object class and a logger.
@@ -467,6 +476,9 @@ var GetLocatorBase = class {
467
476
  }
468
477
  return schemasMap;
469
478
  }
479
+ isLocatorSchemaWithMethods(schema) {
480
+ return REQUIRED_PROPERTIES_FOR_LOCATOR_SCHEMA_WITH_METHODS.every((p) => p in schema);
481
+ }
470
482
  /**
471
483
  * Applies an update to a specific locator schema within the provided map of schemas.
472
484
  * This method ensures that the specified updates are merged into the targeted locator schema.
@@ -474,7 +486,12 @@ var GetLocatorBase = class {
474
486
  applyUpdate(schemasMap, locatorSchemaPath, updateData) {
475
487
  const schema = schemasMap.get(locatorSchemaPath);
476
488
  if (schema) {
477
- schemasMap.set(locatorSchemaPath, this.deepMerge(schema, updateData));
489
+ const updatedSchema = this.deepMerge(schema, updateData);
490
+ if (this.isLocatorSchemaWithMethods(schema)) {
491
+ Object.assign(schema, updatedSchema);
492
+ } else {
493
+ throw new Error("Invalid LocatorSchema object provided for update method.");
494
+ }
478
495
  }
479
496
  }
480
497
  /**
@@ -487,7 +504,12 @@ var GetLocatorBase = class {
487
504
  if (path && updateAtIndex) {
488
505
  const schema = schemasMap.get(path);
489
506
  if (schema) {
490
- schemasMap.set(path, this.deepMerge(schema, updateAtIndex));
507
+ const updatedSchema = this.deepMerge(schema, updateAtIndex);
508
+ if (this.isLocatorSchemaWithMethods(schema)) {
509
+ Object.assign(schema, updatedSchema);
510
+ } else {
511
+ schemasMap.set(path, updatedSchema);
512
+ }
491
513
  }
492
514
  }
493
515
  }
@@ -564,29 +586,45 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
564
586
  throw error;
565
587
  };
566
588
  /** Merges 'source' into 'target', combining their properties into a new isolated object. */
567
- deepMerge(target, source) {
589
+ deepMerge(target, source, schema = getLocatorSchemaDummy()) {
568
590
  const merged = { ...target };
569
- const dummySchema = getLocatorSchemaDummy();
570
- if (typeof source === "object" && source !== null) {
571
- const filteredKeys = Object.keys(source).filter((key) => key !== "locatorSchemaPath");
572
- for (const key of filteredKeys) {
573
- const targetKey = key;
574
- const sourceKey = key;
575
- if (!(key in dummySchema)) {
576
- throw new Error(`Invalid property: '${key}' is not a valid property of LocatorSchema`);
591
+ for (const key of Object.keys(source)) {
592
+ if (key === "locatorSchemaPath") {
593
+ throw new Error(
594
+ `[${this.pageObjectClass.pocName}] Invalid property: 'locatorSchemaPath' cannot be updated. Attempted to update LocatorSchemaPath from '${target[key]}' to '${source[key]}'.`
595
+ );
596
+ }
597
+ if (!(key in schema)) {
598
+ throw new Error(`Invalid property: '${key}' is not a valid property of LocatorSchema`);
599
+ }
600
+ const sourceValue = source[key];
601
+ const targetValue = target[key];
602
+ if (typeof sourceValue === "object" && sourceValue !== null && schema[key] && typeof schema[key] === "object") {
603
+ if (targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) {
604
+ merged[key] = this.deepMerge(
605
+ targetValue,
606
+ // Updated type here
607
+ sourceValue,
608
+ schema[key]
609
+ );
610
+ } else {
611
+ merged[key] = this.deepMerge(
612
+ {},
613
+ // Updated type here
614
+ sourceValue,
615
+ schema[key]
616
+ );
577
617
  }
578
- const targetValue = merged[targetKey];
579
- const sourceValue = source[sourceKey];
580
- if (sourceValue !== void 0) {
581
- if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
582
- merged[targetKey] = [...targetValue, ...sourceValue];
583
- } else if (sourceValue instanceof RegExp) {
584
- merged[targetKey] = new RegExp(sourceValue.source, sourceValue.flags);
585
- } else if (typeof sourceValue === "object" && sourceValue !== null) {
586
- merged[targetKey] = targetValue ? this.deepMerge(targetValue, sourceValue) : structuredClone(sourceValue);
587
- } else {
588
- merged[targetKey] = sourceValue;
589
- }
618
+ } else {
619
+ if (Array.isArray(sourceValue)) {
620
+ merged[key] = Array.isArray(targetValue) ? targetValue.concat(sourceValue) : [...sourceValue];
621
+ } else if (typeof sourceValue === "object" && sourceValue !== null && Object.prototype.toString.call(sourceValue) === "[object RegExp]") {
622
+ merged[key] = new RegExp(
623
+ sourceValue.source,
624
+ sourceValue.flags
625
+ );
626
+ } else {
627
+ merged[key] = sourceValue;
590
628
  }
591
629
  }
592
630
  }
package/dist/index.mjs CHANGED
@@ -376,6 +376,15 @@ var GetBy = class {
376
376
  };
377
377
 
378
378
  // src/helpers/getLocatorBase.ts
379
+ var REQUIRED_PROPERTIES_FOR_LOCATOR_SCHEMA_WITH_METHODS = [
380
+ "update",
381
+ "updates",
382
+ "getNestedLocator",
383
+ "getLocator",
384
+ "locatorSchemaPath",
385
+ "locatorMethod",
386
+ "schemasMap"
387
+ ];
379
388
  var GetLocatorBase = class {
380
389
  /**
381
390
  * Initializes the GetLocatorBase class with a page object class and a logger.
@@ -436,6 +445,9 @@ var GetLocatorBase = class {
436
445
  }
437
446
  return schemasMap;
438
447
  }
448
+ isLocatorSchemaWithMethods(schema) {
449
+ return REQUIRED_PROPERTIES_FOR_LOCATOR_SCHEMA_WITH_METHODS.every((p) => p in schema);
450
+ }
439
451
  /**
440
452
  * Applies an update to a specific locator schema within the provided map of schemas.
441
453
  * This method ensures that the specified updates are merged into the targeted locator schema.
@@ -443,7 +455,12 @@ var GetLocatorBase = class {
443
455
  applyUpdate(schemasMap, locatorSchemaPath, updateData) {
444
456
  const schema = schemasMap.get(locatorSchemaPath);
445
457
  if (schema) {
446
- schemasMap.set(locatorSchemaPath, this.deepMerge(schema, updateData));
458
+ const updatedSchema = this.deepMerge(schema, updateData);
459
+ if (this.isLocatorSchemaWithMethods(schema)) {
460
+ Object.assign(schema, updatedSchema);
461
+ } else {
462
+ throw new Error("Invalid LocatorSchema object provided for update method.");
463
+ }
447
464
  }
448
465
  }
449
466
  /**
@@ -456,7 +473,12 @@ var GetLocatorBase = class {
456
473
  if (path && updateAtIndex) {
457
474
  const schema = schemasMap.get(path);
458
475
  if (schema) {
459
- schemasMap.set(path, this.deepMerge(schema, updateAtIndex));
476
+ const updatedSchema = this.deepMerge(schema, updateAtIndex);
477
+ if (this.isLocatorSchemaWithMethods(schema)) {
478
+ Object.assign(schema, updatedSchema);
479
+ } else {
480
+ schemasMap.set(path, updatedSchema);
481
+ }
460
482
  }
461
483
  }
462
484
  }
@@ -533,29 +555,45 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
533
555
  throw error;
534
556
  };
535
557
  /** Merges 'source' into 'target', combining their properties into a new isolated object. */
536
- deepMerge(target, source) {
558
+ deepMerge(target, source, schema = getLocatorSchemaDummy()) {
537
559
  const merged = { ...target };
538
- const dummySchema = getLocatorSchemaDummy();
539
- if (typeof source === "object" && source !== null) {
540
- const filteredKeys = Object.keys(source).filter((key) => key !== "locatorSchemaPath");
541
- for (const key of filteredKeys) {
542
- const targetKey = key;
543
- const sourceKey = key;
544
- if (!(key in dummySchema)) {
545
- throw new Error(`Invalid property: '${key}' is not a valid property of LocatorSchema`);
560
+ for (const key of Object.keys(source)) {
561
+ if (key === "locatorSchemaPath") {
562
+ throw new Error(
563
+ `[${this.pageObjectClass.pocName}] Invalid property: 'locatorSchemaPath' cannot be updated. Attempted to update LocatorSchemaPath from '${target[key]}' to '${source[key]}'.`
564
+ );
565
+ }
566
+ if (!(key in schema)) {
567
+ throw new Error(`Invalid property: '${key}' is not a valid property of LocatorSchema`);
568
+ }
569
+ const sourceValue = source[key];
570
+ const targetValue = target[key];
571
+ if (typeof sourceValue === "object" && sourceValue !== null && schema[key] && typeof schema[key] === "object") {
572
+ if (targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) {
573
+ merged[key] = this.deepMerge(
574
+ targetValue,
575
+ // Updated type here
576
+ sourceValue,
577
+ schema[key]
578
+ );
579
+ } else {
580
+ merged[key] = this.deepMerge(
581
+ {},
582
+ // Updated type here
583
+ sourceValue,
584
+ schema[key]
585
+ );
546
586
  }
547
- const targetValue = merged[targetKey];
548
- const sourceValue = source[sourceKey];
549
- if (sourceValue !== void 0) {
550
- if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
551
- merged[targetKey] = [...targetValue, ...sourceValue];
552
- } else if (sourceValue instanceof RegExp) {
553
- merged[targetKey] = new RegExp(sourceValue.source, sourceValue.flags);
554
- } else if (typeof sourceValue === "object" && sourceValue !== null) {
555
- merged[targetKey] = targetValue ? this.deepMerge(targetValue, sourceValue) : structuredClone(sourceValue);
556
- } else {
557
- merged[targetKey] = sourceValue;
558
- }
587
+ } else {
588
+ if (Array.isArray(sourceValue)) {
589
+ merged[key] = Array.isArray(targetValue) ? targetValue.concat(sourceValue) : [...sourceValue];
590
+ } else if (typeof sourceValue === "object" && sourceValue !== null && Object.prototype.toString.call(sourceValue) === "[object RegExp]") {
591
+ merged[key] = new RegExp(
592
+ sourceValue.source,
593
+ sourceValue.flags
594
+ );
595
+ } else {
596
+ merged[key] = sourceValue;
559
597
  }
560
598
  }
561
599
  }
package/index.ts CHANGED
@@ -7,7 +7,7 @@ export { test };
7
7
  import { PlaywrightReportLogger } from "./src/helpers/playwrightReportLogger";
8
8
  export { PlaywrightReportLogger };
9
9
 
10
- import { GetByMethod, type LocatorSchema, type AriaRoleType } from "./src/helpers/locatorSchema.interface";
10
+ import { type AriaRoleType, GetByMethod, type LocatorSchema } from "./src/helpers/locatorSchema.interface";
11
11
  export { GetByMethod, type LocatorSchema, type AriaRoleType };
12
12
 
13
13
  import { GetLocatorBase } from "./src/helpers/getLocatorBase";
package/pack-test.sh ADDED
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+
3
+ TEST_DIR="test"
4
+
5
+ # Function to revert to the latest published version, ensuring it's executed in the ./$TEST_DIR directory
6
+ cleanup() {
7
+ # Check if the current directory is ./test, if not, try to change to it
8
+ if [[ $(basename "$PWD") != "$TEST_DIR" ]]; then
9
+ echo "Not in ./$TEST_DIR directory. Trying to change to ./$TEST_DIR directory..."
10
+ if [[ -d "$TEST_DIR" ]]; then
11
+ cd $TEST_DIR || { echo "Failed to change to ./$TEST_DIR directory"; return 1; }
12
+ else
13
+ echo "The ./$TEST_DIR directory does not exist. Exiting cleanup."
14
+ return 1
15
+ fi
16
+ fi
17
+
18
+ echo "Reverting to latest published version of POMWright in the ./$TEST_DIR directory..."
19
+ pnpm i -D pomwright@latest || { echo "Failed to revert to latest POMWright version"; exit 1; }
20
+ }
21
+
22
+ # Trap statement that calls cleanup function on exit
23
+ trap cleanup EXIT
24
+
25
+ # Stop the script if any command fails
26
+ set -e
27
+
28
+ # Extract version from package.json
29
+ VERSION=$(node -pe "require('./package.json').version")
30
+
31
+ # Install, Build & Pack
32
+ pnpm i --frozen-lockfile || { echo "Installation failed"; exit 1; }
33
+ pnpm build || { echo "Build failed"; exit 1; }
34
+ pnpm pack || { echo "Packaging failed"; exit 1; }
35
+
36
+ # Move to the test directory
37
+ cd $TEST_DIR || { echo "Changing directory failed"; exit 1; }
38
+
39
+ # Install the local package
40
+ pnpm i -D ../pomwright-$VERSION.tgz || { echo "Local package installation failed"; exit 1; }
41
+
42
+ # Install dependencies and run playwright tests
43
+ pnpm i --frozen-lockfile || { echo "Installation failed"; exit 1; }
44
+ pnpm playwright install --with-deps || { echo "Playwright dependencies installation failed"; exit 1; }
45
+ pnpm playwright test || { echo "Tests failed"; exit 1; }
46
+
47
+ echo "Testing completed successfully."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pomwright",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "POMWright is a complementary test framework for Playwright written in TypeScript.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,20 +24,24 @@
24
24
  "Test Framework"
25
25
  ],
26
26
  "devDependencies": {
27
- "@biomejs/biome": "^1.5.1",
27
+ "@biomejs/biome": "^1.5.3",
28
+ "@changesets/changelog-github": "^0.5.0",
28
29
  "@changesets/cli": "^2.27.1",
29
30
  "@types/node": "^20.10.8",
30
31
  "tsup": "^8.0.1",
31
- "typescript": "^5.3.3"
32
+ "typescript": "^5.3.3",
33
+ "vitest": "^1.5.0"
32
34
  },
33
35
  "peerDependencies": {
34
- "@playwright/test": "^1.40.1"
36
+ "@playwright/test": "^1.39.0"
35
37
  },
36
38
  "scripts": {
37
39
  "build": "tsup index.ts --format cjs,esm --dts",
38
40
  "release": "pnpm run build && changeset publish",
39
41
  "lint": "biome check ./src",
40
42
  "format": "biome format ./src --write",
41
- "install-browsers": "playwright install --with-deps"
43
+ "install-browsers": "playwright install --with-deps",
44
+ "pack-test": "bash pack-test.sh",
45
+ "test": "vitest run && bash pack-test.sh"
42
46
  }
43
47
  }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "node",
7
+ dir: "src",
8
+ },
9
+ });
@@ -1,5 +0,0 @@
1
- {
2
- "recommendations": [
3
- "biomejs.biome"
4
- ]
5
- }
@@ -1,82 +0,0 @@
1
- {
2
- "Playwright Import": {
3
- "prefix": "pw-import",
4
- "body": [
5
- "import { test, expect } from \"@fixtures/$1\";"
6
- ],
7
- "description": "Import Playwright Test through our Fixtures"
8
- },
9
- "Playwright Describe": {
10
- "prefix": "pw-describe",
11
- "body": [
12
- "test.describe(\"$1\", () => {",
13
- " $2",
14
- "});"
15
- ],
16
- "description": "Playwright Test describe block"
17
- },
18
- "Playwright Test": {
19
- "prefix": "pw-test",
20
- "body": [
21
- "test(\"$1\", async ({ $2 }) => {",
22
- " $3",
23
- "});"
24
- ],
25
- "description": "Playwright Test test block"
26
- },
27
- "Playwright Step": {
28
- "prefix": "pw-step",
29
- "body": [
30
- "await test.step(`${$1.pocName}: $2`, async () => {",
31
- " $3",
32
- "});"
33
- ],
34
- "description": "Playwright Test step block"
35
- },
36
- "Playwright Use": {
37
- "prefix": "pw-use",
38
- "body": [
39
- "test.use({",
40
- " $1",
41
- "});"
42
- ],
43
- "description": "Playwright Test use block"
44
- },
45
- "Playwright beforeEach": {
46
- "prefix": "pw-beforeEach",
47
- "body": [
48
- "test.beforeEach(async ({ $1 }) => {",
49
- " $2",
50
- "});"
51
- ],
52
- "description": "Playwright Test beforeEach block"
53
- },
54
- "Playwright afterEach": {
55
- "prefix": "pw-afterEach",
56
- "body": [
57
- "test.afterEach(async ({ $1 }) => {",
58
- " $2",
59
- "});"
60
- ],
61
- "description": "Playwright Test afterEach block"
62
- },
63
- "Playwright beforeAll": {
64
- "prefix": "pw-beforeAll",
65
- "body": [
66
- "test.beforeAll(async ({ $1 }) => {",
67
- " $2",
68
- "});"
69
- ],
70
- "description": "Playwright Test beforeAll block"
71
- },
72
- "Playwright afterAll": {
73
- "prefix": "pw-afterAll",
74
- "body": [
75
- "test.afterAll(async ({ $1 }) => {",
76
- " $2",
77
- "});"
78
- ],
79
- "description": "Playwright Test afterAll block"
80
- }
81
- }
82
-
@@ -1,4 +0,0 @@
1
- {
2
- "editor.defaultFormatter": "biomejs.biome",
3
- "editor.formatOnSave": true
4
- }
package/example/.env DELETED
@@ -1 +0,0 @@
1
- BASE_URL_SAUCEDEMO="https://www.saucedemo.com"
@@ -1,4 +0,0 @@
1
- import { mergeTests } from "@playwright/test";
2
- import { test as saucedemoTest } from "../page-object-models/saucedemo/fixtures/saucedemo.fixtures";
3
-
4
- export const test = mergeTests(saucedemoTest);
@@ -1,18 +0,0 @@
1
- {
2
- "name": "example",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "index.js",
6
- "scripts": {},
7
- "keywords": [],
8
- "author": "",
9
- "license": "ISC",
10
- "devDependencies": {
11
- "@playwright/test": "^1.40.1",
12
- "@types/node": "^20.11.1",
13
- "pomwright": "^0.0.9"
14
- },
15
- "dependencies": {
16
- "dotenv": "^16.3.1"
17
- }
18
- }
File without changes
@@ -1,28 +0,0 @@
1
- import { type Page, type TestInfo } from "@playwright/test";
2
- import { POMWright, POMWrightLogger } from "pomwright";
3
-
4
- export type UserCredentials = { username: string; password: string };
5
-
6
- export default abstract class Saucedemo<LocatorSchemaPathType extends string> extends POMWright<LocatorSchemaPathType> {
7
- constructor(page: Page, testInfo: TestInfo, urlPath: string, pocName: string, pwrl: POMWrightLogger) {
8
- super(page, testInfo, process.env.BASE_URL_SAUCEDEMO || "", urlPath, pocName, pwrl);
9
- }
10
-
11
- getTestData() {
12
- const passordForAllUsers = "secret_sauce";
13
- return {
14
- user: {
15
- standard: {
16
- username: "standard_user",
17
- password: passordForAllUsers,
18
- } as UserCredentials,
19
- lockedOut: {
20
- username: "locked_out_user",
21
- password: passordForAllUsers,
22
- } as UserCredentials,
23
- },
24
- };
25
- }
26
-
27
- // add your common helper methods here...
28
- }
@@ -1,11 +0,0 @@
1
- import { POMWrightGetLocatorBase } from "pomwright";
2
- import {
3
- type LocatorSchemaPath as headerMenu,
4
- initLocatorSchemas as initHeaderMenu,
5
- } from "./headerMenu/headerMenu.locatorSchema";
6
-
7
- export type LocatorSchemaPath = headerMenu;
8
-
9
- export function initLocatorSchemas(locators: POMWrightGetLocatorBase<LocatorSchemaPath>) {
10
- initHeaderMenu(locators);
11
- }
@@ -1,45 +0,0 @@
1
- import { GetByMethod, POMWrightGetLocatorBase } from "pomwright";
2
-
3
- export type LocatorSchemaPath =
4
- | "common.headerMenu"
5
- | "common.headerMenu.button.burger"
6
- | "common.sidebar"
7
- | "common.sidebar.menu"
8
- | "common.sidebar.menu.link.logout";
9
-
10
- export function initLocatorSchemas(locators: POMWrightGetLocatorBase<LocatorSchemaPath>) {
11
- locators.addSchema("common.headerMenu", {
12
- locator: ".primary_header",
13
- locatorMethod: GetByMethod.locator,
14
- });
15
-
16
- locators.addSchema("common.headerMenu.button.burger", {
17
- role: "button",
18
- roleOptions: {
19
- name: "Open Menu",
20
- },
21
- id: "react-burger-menu-btn",
22
- locatorMethod: GetByMethod.role,
23
- });
24
-
25
- locators.addSchema("common.sidebar", {
26
- locator: ".bm-menu-wrap",
27
- id: "",
28
- locatorMethod: GetByMethod.locator,
29
- });
30
-
31
- locators.addSchema("common.sidebar.menu", {
32
- locator: ".bm-menu",
33
- locatorMethod: GetByMethod.locator,
34
- });
35
-
36
- locators.addSchema("common.sidebar.menu.link.logout", {
37
- role: "link",
38
- roleOptions: {
39
- name: "Logout",
40
- },
41
- text: "Logout",
42
- id: "logout_sidebar_link",
43
- locatorMethod: GetByMethod.text,
44
- });
45
- }
@@ -1,20 +0,0 @@
1
- import { POMWrightTestFixture as base } from "pomwright";
2
- import Home from "../pages/home.page";
3
- import Inventory from "../pages/inventory/inventory.page";
4
-
5
- type fixtures = {
6
- sdHome: Home;
7
- sdInventory: Inventory;
8
- };
9
-
10
- export const test = base.extend<fixtures>({
11
- sdHome: async ({ page, log }, use, testInfo) => {
12
- const sdHome = new Home(page, testInfo, log);
13
- await use(sdHome);
14
- },
15
-
16
- sdInventory: async ({ page, log }, use, testInfo) => {
17
- const sdInventory = new Inventory(page, testInfo, log);
18
- await use(sdInventory);
19
- },
20
- });
@@ -1,99 +0,0 @@
1
- import { GetByMethod, POMWrightGetLocatorBase } from "pomwright";
2
-
3
- export type LocatorSchemaPath =
4
- | "content"
5
- | "content.heading"
6
- | "content.region.login"
7
- | "content.region.login.form"
8
- | "content.region.login.form.input.username"
9
- | "content.region.login.form.input.password"
10
- | "content.region.login.form.error"
11
- | "content.region.login.form.error.lockout"
12
- | "content.region.login.form.button.login"
13
- | "content.region.credentials"
14
- | "content.region.credentials.usernames"
15
- | "content.region.credentials.passwords";
16
-
17
- export function initLocatorSchemas(locators: POMWrightGetLocatorBase<LocatorSchemaPath>) {
18
- locators.addSchema("content", {
19
- locator: ".login_container",
20
- locatorMethod: GetByMethod.locator,
21
- });
22
-
23
- locators.addSchema("content.heading", {
24
- locator: ".login_logo",
25
- text: "Swag Labs",
26
- locatorMethod: GetByMethod.text,
27
- });
28
-
29
- locators.addSchema("content.region.login", {
30
- locator: ".login_wrapper",
31
- locatorMethod: GetByMethod.locator,
32
- });
33
-
34
- locators.addSchema("content.region.login.form", {
35
- locator: "form",
36
- locatorMethod: GetByMethod.locator,
37
- });
38
-
39
- locators.addSchema("content.region.login.form.input.username", {
40
- role: "textbox",
41
- roleOptions: {
42
- name: "Username",
43
- },
44
- placeholder: "Username",
45
- placeholderOptions: {
46
- exact: true,
47
- },
48
- id: "user-name",
49
- locatorMethod: GetByMethod.role,
50
- });
51
-
52
- locators.addSchema("content.region.login.form.input.password", {
53
- role: "textbox",
54
- roleOptions: {
55
- name: "Password",
56
- },
57
- placeholder: "Password",
58
- placeholderOptions: {
59
- exact: true,
60
- },
61
- id: "password",
62
- locatorMethod: GetByMethod.role,
63
- });
64
-
65
- locators.addSchema("content.region.login.form.error", {
66
- locator: ".error-message-container",
67
- locatorMethod: GetByMethod.locator,
68
- });
69
-
70
- locators.addSchema("content.region.login.form.error.lockout", {
71
- text: "Epic sadface: Sorry, this user has been locked out.",
72
- locatorMethod: GetByMethod.text,
73
- });
74
-
75
- locators.addSchema("content.region.login.form.button.login", {
76
- role: "button",
77
- roleOptions: {
78
- name: "Login",
79
- },
80
- id: "login-button",
81
- locatorMethod: GetByMethod.role,
82
- });
83
-
84
- locators.addSchema("content.region.credentials", {
85
- locator: ".login_credentials_wrap",
86
- locatorMethod: GetByMethod.locator,
87
- });
88
-
89
- locators.addSchema("content.region.credentials.usernames", {
90
- locator: ".login_credentials",
91
- id: "login_credentials",
92
- locatorMethod: GetByMethod.locator,
93
- });
94
-
95
- locators.addSchema("content.region.credentials.passwords", {
96
- locator: ".login_password",
97
- locatorMethod: GetByMethod.locator,
98
- });
99
- }
@@ -1,29 +0,0 @@
1
- import test, { type Page, type TestInfo } from "@playwright/test";
2
- import { POMWrightLogger } from "pomwright";
3
- import Saucedemo, { type UserCredentials } from "../common/base-page/saucedemo.page";
4
- import { type LocatorSchemaPath, initLocatorSchemas } from "./home.locatorSchema";
5
-
6
- export default class Home extends Saucedemo<LocatorSchemaPath> {
7
- constructor(page: Page, testInfo: TestInfo, pwrl: POMWrightLogger) {
8
- super(page, testInfo, "/", Home.name, pwrl);
9
- }
10
-
11
- protected initLocatorSchemas() {
12
- initLocatorSchemas(this.locators);
13
- }
14
-
15
- async fillLoginForm(userCredentials: UserCredentials) {
16
- await test.step(`${this.pocName}: Fill user login credentials and login`, async () => {
17
- const username = await this.getNestedLocator("content.region.login.form.input.username");
18
- await username.fill(userCredentials.username);
19
-
20
- const password = await this.getNestedLocator("content.region.login.form.input.password");
21
- await password.fill(userCredentials.password);
22
-
23
- const loginButton = await this.getNestedLocator("content.region.login.form.button.login");
24
- await loginButton.click();
25
- });
26
- }
27
-
28
- // add your helper methods here...
29
- }
@@ -1,22 +0,0 @@
1
- import { GetByMethod, POMWrightGetLocatorBase } from "pomwright";
2
- import {
3
- type LocatorSchemaPath as commmon,
4
- initLocatorSchemas as initCommon,
5
- } from "../../common/page-components/headerMenu/headerMenu.locatorSchema";
6
-
7
- export type LocatorSchemaPath = commmon | "inventory" | "inventory.container" | "inventory.container.list";
8
-
9
- export function initLocatorSchemas(locators: POMWrightGetLocatorBase<LocatorSchemaPath>) {
10
- initCommon(locators);
11
-
12
- locators.addSchema("inventory", {
13
- id: "inventory_container", // duplicate, will resolve to multiple locators
14
- locatorMethod: GetByMethod.id,
15
- });
16
-
17
- locators.addSchema("inventory.container", {
18
- locator: ".inventory_container",
19
- id: "inventory_container", // duplicate, will resolve to multiple locators
20
- locatorMethod: GetByMethod.locator,
21
- });
22
- }
@@ -1,26 +0,0 @@
1
- import test, { type Page, type TestInfo } from "@playwright/test";
2
- import { POMWrightLogger } from "pomwright";
3
- import Saucedemo from "../../common/base-page/saucedemo.page";
4
- import { type LocatorSchemaPath, initLocatorSchemas } from "./inventory.locatorSchema";
5
-
6
- export default class Inventory extends Saucedemo<LocatorSchemaPath> {
7
- constructor(page: Page, testInfo: TestInfo, pwrl: POMWrightLogger) {
8
- super(page, testInfo, "/inventory.html", Inventory.name, pwrl);
9
- }
10
-
11
- protected initLocatorSchemas() {
12
- initLocatorSchemas(this.locators);
13
- }
14
-
15
- async logout() {
16
- await test.step(`${this.pocName}: Logout user from inventory page`, async () => {
17
- const burgerMenyBtn = await this.getNestedLocator("common.headerMenu.button.burger");
18
- await burgerMenyBtn.click();
19
-
20
- const logoutLink = await this.getNestedLocator("common.sidebar.menu.link.logout");
21
- await logoutLink.click();
22
- });
23
- }
24
-
25
- // add your helper methods here...
26
- }
@@ -1,63 +0,0 @@
1
- import { defineConfig, devices } from "@playwright/test";
2
- import dotenv from "dotenv";
3
-
4
- // Environment variables ./.env
5
- dotenv.config({ override: false });
6
-
7
- /**
8
- * See https://playwright.dev/docs/test-configuration.
9
- */
10
- export default defineConfig({
11
- testDir: "./tests",
12
- globalTimeout: 60_000 * 30,
13
- timeout: 60_000 * 2,
14
- expect: {
15
- timeout: 10_000,
16
- },
17
- fullyParallel: true,
18
- forbidOnly: !!process.env.CI,
19
- retries: process.env.CI ? 2 : 1,
20
- workers: process.env.CI ? "50%" : 4,
21
- reporter: process.env.CI
22
- ? [
23
- ["html", { open: "never" }],
24
- ["github", { printSteps: false }],
25
- ]
26
- : [
27
- ["html", { open: "on-failure" }],
28
- ["list", { printSteps: false }],
29
- ],
30
- use: {
31
- actionTimeout: 10_000,
32
- navigationTimeout: 30_000,
33
- headless: true,
34
- viewport: { width: 1280, height: 720 },
35
- ignoreHTTPSErrors: false,
36
- video: "retry-with-video", // system taxing
37
- screenshot: { mode: "only-on-failure", fullPage: true, omitBackground: false },
38
- trace: "on-all-retries", // system taxing
39
- testIdAttribute: "data-testid", // Playwright default
40
- // locale: "nb-NO",
41
- // baseURL: process.env.BASE_URL // we do not want to use this if we want to test multiple-domains, make an abstract class per domain extending POMWright instead
42
- },
43
-
44
- /* Configure projects for major browsers */
45
- projects: [
46
- {
47
- name: "chromium",
48
- use: { ...devices["Desktop Chrome"] },
49
- },
50
-
51
- {
52
- name: "firefox",
53
- use: { ...devices["Desktop Firefox"] },
54
- },
55
-
56
- {
57
- name: "webkit",
58
- use: { ...devices["Desktop Safari"] },
59
- },
60
- ],
61
-
62
- // webServer: []
63
- });