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.
- package/CHANGELOG.md +37 -0
- package/README.md +1 -1
- package/biome.json +9 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +61 -23
- package/dist/index.mjs +61 -23
- package/index.ts +1 -1
- package/pack-test.sh +47 -0
- package/package.json +9 -5
- package/vitest.config.ts +9 -0
- package/.vscode/extensions.json +0 -5
- package/.vscode/playwright-snippets.code-snippets +0 -82
- package/.vscode/settings.json +0 -4
- package/example/.env +0 -1
- package/example/fixtures/all.fixtures.ts +0 -4
- package/example/package.json +0 -18
- package/example/page-object-models/anotherDomain/.gitkeep +0 -0
- package/example/page-object-models/saucedemo/common/base-page/saucedemo.page.ts +0 -28
- package/example/page-object-models/saucedemo/common/page-components/common.locatorScema.ts +0 -11
- package/example/page-object-models/saucedemo/common/page-components/headerMenu/headerMenu.locatorSchema.ts +0 -45
- package/example/page-object-models/saucedemo/common/pages/.gitkeep +0 -0
- package/example/page-object-models/saucedemo/common/utils/.gitkeep +0 -0
- package/example/page-object-models/saucedemo/fixtures/saucedemo.fixtures.ts +0 -20
- package/example/page-object-models/saucedemo/pages/home.locatorSchema.ts +0 -99
- package/example/page-object-models/saucedemo/pages/home.page.ts +0 -29
- package/example/page-object-models/saucedemo/pages/inventory/inventory.locatorSchema.ts +0 -22
- package/example/page-object-models/saucedemo/pages/inventory/inventory.page.ts +0 -26
- 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
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|
package/vitest.config.ts
ADDED
package/.vscode/extensions.json
DELETED
|
@@ -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
|
-
|
package/.vscode/settings.json
DELETED
package/example/.env
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
BASE_URL_SAUCEDEMO="https://www.saucedemo.com"
|
package/example/package.json
DELETED
|
@@ -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
|
-
}
|
|
File without changes
|
|
File without changes
|
|
@@ -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
|
-
});
|