@qawolf/pom 0.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/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/basePageObject.d.ts +50 -0
- package/dist/basePageObject.d.ts.map +1 -0
- package/dist/basePageObject.js +41 -0
- package/dist/basePageObject.js.map +1 -0
- package/dist/cleanupUtils.d.ts +29 -0
- package/dist/cleanupUtils.d.ts.map +1 -0
- package/dist/cleanupUtils.js +62 -0
- package/dist/cleanupUtils.js.map +1 -0
- package/dist/entryPointPageObject.d.ts +44 -0
- package/dist/entryPointPageObject.d.ts.map +1 -0
- package/dist/entryPointPageObject.js +116 -0
- package/dist/entryPointPageObject.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/networkMonitor.d.ts +35 -0
- package/dist/networkMonitor.d.ts.map +1 -0
- package/dist/networkMonitor.js +70 -0
- package/dist/networkMonitor.js.map +1 -0
- package/dist/pageHookCollection.d.ts +20 -0
- package/dist/pageHookCollection.d.ts.map +1 -0
- package/dist/pageHookCollection.js +29 -0
- package/dist/pageHookCollection.js.map +1 -0
- package/dist/pageHooks.d.ts +43 -0
- package/dist/pageHooks.d.ts.map +1 -0
- package/dist/pageHooks.js +2 -0
- package/dist/pageHooks.js.map +1 -0
- package/dist/pageRegistry.d.ts +58 -0
- package/dist/pageRegistry.d.ts.map +1 -0
- package/dist/pageRegistry.js +79 -0
- package/dist/pageRegistry.js.map +1 -0
- package/dist/platformClient.d.ts +11 -0
- package/dist/platformClient.d.ts.map +1 -0
- package/dist/platformClient.js +27 -0
- package/dist/platformClient.js.map +1 -0
- package/dist/popupHandler.d.ts +18 -0
- package/dist/popupHandler.d.ts.map +1 -0
- package/dist/popupHandler.js +46 -0
- package/dist/popupHandler.js.map +1 -0
- package/dist/popupShieldInitScript.d.ts +11 -0
- package/dist/popupShieldInitScript.d.ts.map +1 -0
- package/dist/popupShieldInitScript.js +45 -0
- package/dist/popupShieldInitScript.js.map +1 -0
- package/dist/sequence.d.ts +2 -0
- package/dist/sequence.d.ts.map +1 -0
- package/dist/sequence.js +2 -0
- package/dist/sequence.js.map +1 -0
- package/dist/subPageObject.d.ts +9 -0
- package/dist/subPageObject.d.ts.map +1 -0
- package/dist/subPageObject.js +9 -0
- package/dist/subPageObject.js.map +1 -0
- package/dist/testDataUtilities.d.ts +7 -0
- package/dist/testDataUtilities.d.ts.map +1 -0
- package/dist/testDataUtilities.js +21 -0
- package/dist/testDataUtilities.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 QA Wolf Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @qawolf/pom
|
|
2
|
+
|
|
3
|
+
`@qawolf/pom` is a TypeScript toolkit for building Page Object Models (POMs) on
|
|
4
|
+
top of Playwright: base classes for page objects, a central registry that lets
|
|
5
|
+
page objects construct one another without circular imports, and automatic
|
|
6
|
+
popup and route-hook installation.
|
|
7
|
+
|
|
8
|
+
To learn more about how to use `@qawolf/pom`, check out the
|
|
9
|
+
[API Reference](https://docs.qawolf.com/qawolf/libraries/pom/api-reference/index)
|
|
10
|
+
and [Documentation](https://docs.qawolf.com/qawolf/libraries/pom).
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @qawolf/pom
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Define a page object by extending `BasePageObject`, keeping selectors in a
|
|
21
|
+
private `locators` getter and constructing sibling page objects with
|
|
22
|
+
`this.create(...)`.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { BasePageObject } from "@qawolf/pom";
|
|
26
|
+
|
|
27
|
+
export class LoginPage extends BasePageObject {
|
|
28
|
+
private get locators() {
|
|
29
|
+
return {
|
|
30
|
+
email: this.page.getByLabel("Email"),
|
|
31
|
+
submit: this.page.getByRole("button", { name: "Sign in" }),
|
|
32
|
+
} as const;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async signIn(email: string) {
|
|
36
|
+
await this.locators.email.fill(email);
|
|
37
|
+
await this.locators.submit.click();
|
|
38
|
+
return this.create("DashboardPage");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Register every page object once — importing the registration module for its
|
|
44
|
+
side effects before constructing any page object — then build one for a
|
|
45
|
+
Playwright `page`.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { createPage, registerPage } from "@qawolf/pom";
|
|
49
|
+
|
|
50
|
+
registerPage("LoginPage", () => import("./pages/login-page.js"));
|
|
51
|
+
|
|
52
|
+
const loginPage = await createPage("LoginPage", page);
|
|
53
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Locator, Page } from "playwright";
|
|
2
|
+
import type { RegisteredPages } from "./index.js";
|
|
3
|
+
import type { PopupHandlerDef, RouteInterceptorDef } from "./pageHooks.js";
|
|
4
|
+
export type BaselineScreenshotFn = (target: Page | Locator, name: string, options?: Record<string, unknown>) => Promise<void>;
|
|
5
|
+
export declare abstract class BasePageObject {
|
|
6
|
+
protected page: Page;
|
|
7
|
+
protected constructor(page: Page);
|
|
8
|
+
/**
|
|
9
|
+
* Public factory for the page registry while keeping constructors protected.
|
|
10
|
+
*/
|
|
11
|
+
static createFromPage<TPageObject extends BasePageObject>(this: new (page: Page) => TPageObject, page: Page): TPageObject;
|
|
12
|
+
/**
|
|
13
|
+
* Popups owned by this POM that should be auto-dismissed when the entry
|
|
14
|
+
* point installs page hooks. Override in subclasses that own popups; the
|
|
15
|
+
* page registry detects the override via an own-property check on the
|
|
16
|
+
* registered class's prototype at registration time, so no extra wiring is
|
|
17
|
+
* needed in `register-pages.ts`. Inherited overrides are NOT picked up —
|
|
18
|
+
* declare `popupHandlers()` directly on the class you register (see
|
|
19
|
+
* `pageRegistry.ts` for details).
|
|
20
|
+
*
|
|
21
|
+
* Override bodies typically read from `this.locators` / `this.dynamicLocators`.
|
|
22
|
+
*/
|
|
23
|
+
popupHandlers(): PopupHandlerDef[];
|
|
24
|
+
/**
|
|
25
|
+
* Route interceptors owned by this POM that should be installed when the
|
|
26
|
+
* entry point installs page hooks. Override in subclasses; the page
|
|
27
|
+
* registry auto-detects the override the same way it does for
|
|
28
|
+
* `popupHandlers` (own-property check on the registered class's
|
|
29
|
+
* prototype — inherited overrides are not picked up).
|
|
30
|
+
*/
|
|
31
|
+
routeInterceptors(): RouteInterceptorDef[];
|
|
32
|
+
/**
|
|
33
|
+
* Construct a sibling POM that shares this page instance.
|
|
34
|
+
*
|
|
35
|
+
* This is the ONLY way POMs should create other POMs — it routes through
|
|
36
|
+
* the central page registry, eliminating circular import dependencies.
|
|
37
|
+
* POMs never `import { SiblingPage }` as a value; they use
|
|
38
|
+
* `import type { SiblingPage }` for return-type annotations and call
|
|
39
|
+
* `await this.create('SiblingPage')` for runtime construction. The call
|
|
40
|
+
* is async because lazily registered POM modules load on first use.
|
|
41
|
+
*
|
|
42
|
+
* When the workspace's `register-pages.ts` augments `RegisteredPages`,
|
|
43
|
+
* names in the map return their page-object type without an annotation;
|
|
44
|
+
* other names default to `BasePageObject`, so annotate the call —
|
|
45
|
+
* `this.create<NextPage>("NextPage")` — for a more specific type.
|
|
46
|
+
*/
|
|
47
|
+
protected create<TName extends keyof RegisteredPages & string>(name: TName): Promise<RegisteredPages[TName]>;
|
|
48
|
+
protected create<TPageObject extends BasePageObject = BasePageObject>(name: string): Promise<TPageObject>;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=basePageObject.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"basePageObject.d.ts","sourceRoot":"","sources":["../src/basePageObject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG3E,MAAM,MAAM,oBAAoB,GAAG,CACjC,MAAM,EAAE,IAAI,GAAG,OAAO,EACtB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,8BAAsB,cAAc;IAClC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC;IAErB,SAAS,aAAa,IAAI,EAAE,IAAI;IAIhC;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,WAAW,SAAS,cAAc,EACtD,IAAI,EAAE,KAAK,IAAI,EAAE,IAAI,KAAK,WAAW,EACrC,IAAI,EAAE,IAAI,GACT,WAAW;IAId;;;;;;;;;;OAUG;IACH,aAAa,IAAI,eAAe,EAAE;IAIlC;;;;;;OAMG;IACH,iBAAiB,IAAI,mBAAmB,EAAE;IAI1C;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,MAAM,CAAC,KAAK,SAAS,MAAM,eAAe,GAAG,MAAM,EAC3D,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAClC,SAAS,CAAC,MAAM,CAAC,WAAW,SAAS,cAAc,GAAG,cAAc,EAClE,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,WAAW,CAAC;CAIxB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createPage } from "./pageRegistry.js";
|
|
2
|
+
export class BasePageObject {
|
|
3
|
+
page;
|
|
4
|
+
constructor(page) {
|
|
5
|
+
this.page = page;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Public factory for the page registry while keeping constructors protected.
|
|
9
|
+
*/
|
|
10
|
+
static createFromPage(page) {
|
|
11
|
+
return new this(page);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Popups owned by this POM that should be auto-dismissed when the entry
|
|
15
|
+
* point installs page hooks. Override in subclasses that own popups; the
|
|
16
|
+
* page registry detects the override via an own-property check on the
|
|
17
|
+
* registered class's prototype at registration time, so no extra wiring is
|
|
18
|
+
* needed in `register-pages.ts`. Inherited overrides are NOT picked up —
|
|
19
|
+
* declare `popupHandlers()` directly on the class you register (see
|
|
20
|
+
* `pageRegistry.ts` for details).
|
|
21
|
+
*
|
|
22
|
+
* Override bodies typically read from `this.locators` / `this.dynamicLocators`.
|
|
23
|
+
*/
|
|
24
|
+
popupHandlers() {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Route interceptors owned by this POM that should be installed when the
|
|
29
|
+
* entry point installs page hooks. Override in subclasses; the page
|
|
30
|
+
* registry auto-detects the override the same way it does for
|
|
31
|
+
* `popupHandlers` (own-property check on the registered class's
|
|
32
|
+
* prototype — inherited overrides are not picked up).
|
|
33
|
+
*/
|
|
34
|
+
routeInterceptors() {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
create(name) {
|
|
38
|
+
return createPage(name, this.page);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=basePageObject.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"basePageObject.js","sourceRoot":"","sources":["../src/basePageObject.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAQ/C,MAAM,OAAgB,cAAc;IACxB,IAAI,CAAO;IAErB,YAAsB,IAAU;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,cAAc,CAEnB,IAAU;QAEV,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAED;;;;;;;;;;OAUG;IACH,aAAa;QACX,OAAO,EAAE,CAAC;IACZ,CAAC;IAED;;;;;;OAMG;IACH,iBAAiB;QACf,OAAO,EAAE,CAAC;IACZ,CAAC;IAuBS,MAAM,CAAC,IAAY;QAC3B,OAAO,UAAU,CAAiB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleanup Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides fail-safe reporting for cleanup sequence failures.
|
|
5
|
+
* Cleanup sequences reset server-side state (cart, addresses, prefs)
|
|
6
|
+
* between test runs. When a cleanup fails, it is logged but does NOT
|
|
7
|
+
* abort the test — the test continues and the failure is reported
|
|
8
|
+
* separately for investigation.
|
|
9
|
+
*
|
|
10
|
+
* API endpoint documentation: https://task-wolf.com/docs/users/automation/apis/cleanup-alerts/
|
|
11
|
+
*/
|
|
12
|
+
export type ReportCleanupFailedParams = {
|
|
13
|
+
dedupKey?: string;
|
|
14
|
+
errorMsg?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* POST cleanup failure details to the automation Cleanup Failure API.
|
|
18
|
+
* No-ops when `QAWOLF_RUN_ID` is unset (e.g. editor runs).
|
|
19
|
+
*/
|
|
20
|
+
export declare function reportCleanupFailed({ dedupKey, errorMsg, }?: ReportCleanupFailedParams): Promise<unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* Report a cleanup sequence failure to the automation stack.
|
|
23
|
+
* Logs the error but does NOT throw — the test continues.
|
|
24
|
+
*
|
|
25
|
+
* @param cleanupName - Identifier for the cleanup (e.g., "cart-cleanup")
|
|
26
|
+
* @param error - The error thrown by the cleanup sequence
|
|
27
|
+
*/
|
|
28
|
+
export declare function reportCleanupFailure(cleanupName: string, error: unknown): void;
|
|
29
|
+
//# sourceMappingURL=cleanupUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanupUtils.d.ts","sourceRoot":"","sources":["../src/cleanupUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,QAAQ,EACR,QAAQ,GACT,GAAE,yBAA8B,GAAG,OAAO,CAAC,OAAO,CAAC,CA0BnD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,GACb,IAAI,CAeN"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleanup Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides fail-safe reporting for cleanup sequence failures.
|
|
5
|
+
* Cleanup sequences reset server-side state (cart, addresses, prefs)
|
|
6
|
+
* between test runs. When a cleanup fails, it is logged but does NOT
|
|
7
|
+
* abort the test — the test continues and the failure is reported
|
|
8
|
+
* separately for investigation.
|
|
9
|
+
*
|
|
10
|
+
* API endpoint documentation: https://task-wolf.com/docs/users/automation/apis/cleanup-alerts/
|
|
11
|
+
*/
|
|
12
|
+
const cleanupApiUrl = "https://task-wolf.com/apis/cleanup-fail";
|
|
13
|
+
/**
|
|
14
|
+
* POST cleanup failure details to the automation Cleanup Failure API.
|
|
15
|
+
* No-ops when `QAWOLF_RUN_ID` is unset (e.g. editor runs).
|
|
16
|
+
*/
|
|
17
|
+
export async function reportCleanupFailed({ dedupKey, errorMsg, } = {}) {
|
|
18
|
+
const payload = {
|
|
19
|
+
dedupKey,
|
|
20
|
+
errorMsg,
|
|
21
|
+
runId: process.env.QAWOLF_RUN_ID,
|
|
22
|
+
suiteId: process.env.QAWOLF_SUITE_ID,
|
|
23
|
+
teamId: process.env.QAWOLF_TEAM_ID,
|
|
24
|
+
workflowId: process.env.QAWOLF_WORKFLOW_ID,
|
|
25
|
+
};
|
|
26
|
+
if (!payload.runId)
|
|
27
|
+
return;
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(cleanupApiUrl, {
|
|
30
|
+
body: JSON.stringify(payload),
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
method: "POST",
|
|
33
|
+
});
|
|
34
|
+
if (response.ok)
|
|
35
|
+
return await response.json();
|
|
36
|
+
throw Error(`HTTP error! Status: ${response.status}`);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
40
|
+
throw Error(`Fetch or parsing error! ${msg}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Report a cleanup sequence failure to the automation stack.
|
|
45
|
+
* Logs the error but does NOT throw — the test continues.
|
|
46
|
+
*
|
|
47
|
+
* @param cleanupName - Identifier for the cleanup (e.g., "cart-cleanup")
|
|
48
|
+
* @param error - The error thrown by the cleanup sequence
|
|
49
|
+
*/
|
|
50
|
+
export function reportCleanupFailure(cleanupName, error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
53
|
+
console.warn(`[CLEANUP FAILED] ${cleanupName}: ${message}`);
|
|
54
|
+
if (stack)
|
|
55
|
+
console.warn(`[CLEANUP STACK] ${stack}`);
|
|
56
|
+
const errorMsg = stack ? `${message}\n${stack}` : message;
|
|
57
|
+
reportCleanupFailed({ dedupKey: cleanupName, errorMsg }).catch((reportErr) => {
|
|
58
|
+
const reportMsg = reportErr instanceof Error ? reportErr.message : String(reportErr);
|
|
59
|
+
console.warn(`[CLEANUP REPORT FAILED] ${reportMsg}`);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=cleanupUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanupUtils.js","sourceRoot":"","sources":["../src/cleanupUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,aAAa,GAAG,yCAAyC,CAAC;AAOhE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,EACxC,QAAQ,EACR,QAAQ,MACqB,EAAE;IAC/B,MAAM,OAAO,GAAG;QACd,QAAQ;QACR,QAAQ;QACR,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa;QAChC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe;QACpC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;QAClC,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB;KAC3C,CAAC;IAEF,IAAI,CAAC,OAAO,CAAC,KAAK;QAAE,OAAO;IAE3B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,aAAa,EAAE;YAC1C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;YAC7B,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;QAEH,IAAI,QAAQ,CAAC,EAAE;YAAE,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAE9C,MAAM,KAAK,CAAC,uBAAuB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnE,MAAM,KAAK,CAAC,2BAA2B,GAAG,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,WAAmB,EACnB,KAAc;IAEd,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,MAAM,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAE/D,OAAO,CAAC,IAAI,CAAC,oBAAoB,WAAW,KAAK,OAAO,EAAE,CAAC,CAAC;IAC5D,IAAI,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,mBAAmB,KAAK,EAAE,CAAC,CAAC;IAEpD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAC1D,mBAAmB,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAC5D,CAAC,SAAS,EAAE,EAAE;QACZ,MAAM,SAAS,GACb,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,2BAA2B,SAAS,EAAE,CAAC,CAAC;IACvD,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Page } from "playwright";
|
|
2
|
+
import { BasePageObject } from "./basePageObject.js";
|
|
3
|
+
import type { PageSetupOptions } from "./pageHooks.js";
|
|
4
|
+
export declare abstract class EntryPointPageObject extends BasePageObject {
|
|
5
|
+
protected static initializeBrowser(options?: InitializeBrowserOptions): Promise<Page>;
|
|
6
|
+
/**
|
|
7
|
+
* Navigate to a URL (defaults to DEFAULT_URL from .env). Accepts Playwright's
|
|
8
|
+
* `page.goto` options; `waitUntil: "domcontentloaded"` and `timeout: 60000`
|
|
9
|
+
* are the defaults, overridable per call.
|
|
10
|
+
*/
|
|
11
|
+
goto(url?: string, options?: Parameters<Page["goto"]>[1]): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Install all page hooks. Call before goto().
|
|
14
|
+
*
|
|
15
|
+
* Hook defs come from the page registry — every POM that overrode
|
|
16
|
+
* `popupHandlers()` or `routeInterceptors()` on its class contributes
|
|
17
|
+
* here, not just the entry point. See `pageHookCollection.ts` for the
|
|
18
|
+
* auto-detection mechanism.
|
|
19
|
+
*/
|
|
20
|
+
protected installPageHooks(options?: PageSetupOptions): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export type InitializeBrowserOptions = {
|
|
23
|
+
device?: BrowserDeviceOverride;
|
|
24
|
+
permissions?: string[];
|
|
25
|
+
proxy?: BrowserProxyConfig;
|
|
26
|
+
slowMo?: number;
|
|
27
|
+
};
|
|
28
|
+
type BrowserDeviceOverride = {
|
|
29
|
+
deviceScaleFactor?: number;
|
|
30
|
+
hasTouch?: boolean;
|
|
31
|
+
isMobile?: boolean;
|
|
32
|
+
userAgent?: string;
|
|
33
|
+
viewport?: {
|
|
34
|
+
height: number;
|
|
35
|
+
width: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
type BrowserProxyConfig = {
|
|
39
|
+
password?: string;
|
|
40
|
+
server: string;
|
|
41
|
+
username?: string;
|
|
42
|
+
};
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=entryPointPageObject.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entryPointPageObject.d.ts","sourceRoot":"","sources":["../src/entryPointPageObject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA0CvD,8BAAsB,oBAAqB,SAAQ,cAAc;qBACxC,iBAAiB,CACtC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,IAAI,CAAC;IAYhB;;;;OAIG;IACG,IAAI,CACR,GAAG,CAAC,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC;IAiBhB;;;;;;;OAOG;cACa,gBAAgB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;CA6C5E;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { launch } from "@qawolf/flows/web";
|
|
2
|
+
import { BasePageObject } from "./basePageObject.js";
|
|
3
|
+
import { getRegisteredPopupHandlers, getRegisteredRouteInterceptors, } from "./pageHookCollection.js";
|
|
4
|
+
import { buildPopupShieldInitScript } from "./popupShieldInitScript.js";
|
|
5
|
+
/**
|
|
6
|
+
* Playwright applies HTTP auth when `httpCredentials` is set. Passing empty
|
|
7
|
+
* username/password (e.g. from unset env vars coerced with `?? ""`) breaks
|
|
8
|
+
* navigation on sites that do not expect preemptive auth — only include
|
|
9
|
+
* credentials when both `AUTH_USERNAME` and `AUTH_PASSWORD` are non-empty.
|
|
10
|
+
*/
|
|
11
|
+
function optionalHttpCredentialsFromEnv() {
|
|
12
|
+
const username = process.env.AUTH_USERNAME;
|
|
13
|
+
const password = process.env.AUTH_PASSWORD;
|
|
14
|
+
if (username && password)
|
|
15
|
+
return { httpCredentials: { password, username } };
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
function assertUniqueHookNames(groups, kind) {
|
|
19
|
+
const seen = new Map();
|
|
20
|
+
for (const { className, defs } of groups) {
|
|
21
|
+
for (const def of defs) {
|
|
22
|
+
const prev = seen.get(def.name);
|
|
23
|
+
if (prev !== undefined) {
|
|
24
|
+
if (prev === className) {
|
|
25
|
+
throw Error(`Duplicate ${kind} hook name "${def.name}" defined twice on ${className}.`);
|
|
26
|
+
}
|
|
27
|
+
throw Error(`Duplicate ${kind} hook name "${def.name}" registered by both ${prev} and ${className}.`);
|
|
28
|
+
}
|
|
29
|
+
seen.set(def.name, className);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class EntryPointPageObject extends BasePageObject {
|
|
34
|
+
static async initializeBrowser(options = {}) {
|
|
35
|
+
const launchResult = await launch({
|
|
36
|
+
...options,
|
|
37
|
+
...optionalHttpCredentialsFromEnv(),
|
|
38
|
+
});
|
|
39
|
+
if (!("browser" in launchResult))
|
|
40
|
+
throw Error("Expected a browser launch result for QAW platform context.");
|
|
41
|
+
return launchResult.context.newPage();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Navigate to a URL (defaults to DEFAULT_URL from .env). Accepts Playwright's
|
|
45
|
+
* `page.goto` options; `waitUntil: "domcontentloaded"` and `timeout: 60000`
|
|
46
|
+
* are the defaults, overridable per call.
|
|
47
|
+
*/
|
|
48
|
+
async goto(url, options) {
|
|
49
|
+
const targetUrl = url || process.env.DEFAULT_URL;
|
|
50
|
+
if (!targetUrl) {
|
|
51
|
+
throw Error("No URL provided and no DEFAULT_URL environment variable is set");
|
|
52
|
+
}
|
|
53
|
+
await this.page.goto(targetUrl, {
|
|
54
|
+
timeout: 60000,
|
|
55
|
+
waitUntil: "domcontentloaded",
|
|
56
|
+
...options,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Concrete entry points define `static async create()` using `initializeBrowser`,
|
|
60
|
+
// `new this(page)`, and `installPageHooks`.
|
|
61
|
+
/**
|
|
62
|
+
* Install all page hooks. Call before goto().
|
|
63
|
+
*
|
|
64
|
+
* Hook defs come from the page registry — every POM that overrode
|
|
65
|
+
* `popupHandlers()` or `routeInterceptors()` on its class contributes
|
|
66
|
+
* here, not just the entry point. See `pageHookCollection.ts` for the
|
|
67
|
+
* auto-detection mechanism.
|
|
68
|
+
*/
|
|
69
|
+
async installPageHooks(options) {
|
|
70
|
+
const skipPopups = new Set(options?.allowPopups);
|
|
71
|
+
const skipRoutes = new Set(options?.allowRoutes);
|
|
72
|
+
const popupGroups = await getRegisteredPopupHandlers(this.page);
|
|
73
|
+
const routeGroups = await getRegisteredRouteInterceptors(this.page);
|
|
74
|
+
assertUniqueHookNames(popupGroups, "popup");
|
|
75
|
+
assertUniqueHookNames(routeGroups, "route");
|
|
76
|
+
const popupDefs = popupGroups.flatMap((g) => g.defs);
|
|
77
|
+
const routeDefs = routeGroups.flatMap((g) => g.defs);
|
|
78
|
+
if (options?.handler) {
|
|
79
|
+
options.handler.install(this.page);
|
|
80
|
+
for (const def of popupDefs) {
|
|
81
|
+
if (skipPopups.has(def.name))
|
|
82
|
+
continue;
|
|
83
|
+
await options.handler.add(def.name, def.trigger, def.dismiss);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const cssSelectors = [];
|
|
88
|
+
for (const def of popupDefs) {
|
|
89
|
+
if (skipPopups.has(def.name))
|
|
90
|
+
continue;
|
|
91
|
+
if (def.cssSelector)
|
|
92
|
+
cssSelectors.push(def.cssSelector);
|
|
93
|
+
// A popup without a cssSelector cannot use the CSS-injection shield, so
|
|
94
|
+
// it falls back to addLocatorHandler. In default mode the shield is
|
|
95
|
+
// preferred because addLocatorHandler deadlocks when multiple overlays
|
|
96
|
+
// stack — each handler waits for the action pipeline, blocking all
|
|
97
|
+
// locator operations (evaluate, count, screenshot).
|
|
98
|
+
else {
|
|
99
|
+
await this.page.addLocatorHandler(def.trigger, async () => {
|
|
100
|
+
await def.dismiss();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (cssSelectors.length > 0)
|
|
105
|
+
await this.page.addInitScript(buildPopupShieldInitScript(cssSelectors));
|
|
106
|
+
}
|
|
107
|
+
for (const routeDef of routeDefs) {
|
|
108
|
+
if (skipRoutes.has(routeDef.name))
|
|
109
|
+
continue;
|
|
110
|
+
await this.page.route(routeDef.pattern, routeDef.handler);
|
|
111
|
+
}
|
|
112
|
+
if (options?.monitor)
|
|
113
|
+
options.monitor.install(this.page);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=entryPointPageObject.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entryPointPageObject.js","sourceRoot":"","sources":["../src/entryPointPageObject.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map of page-object types keyed by the name passed to `registerPage`.
|
|
3
|
+
*
|
|
4
|
+
* Empty by default. A workspace opts into typed `this.create("ClassName")`
|
|
5
|
+
* by augmenting it from its generated `register-pages.ts`:
|
|
6
|
+
*
|
|
7
|
+
* declare module "@qawolf/pom" {
|
|
8
|
+
* interface RegisteredPages {
|
|
9
|
+
* LoginPage: LoginPage;
|
|
10
|
+
* }
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* `this.create("LoginPage")` then returns `LoginPage` without a type
|
|
14
|
+
* annotation. Names missing from the map keep the untyped fallback, so the
|
|
15
|
+
* map can be adopted incrementally.
|
|
16
|
+
*
|
|
17
|
+
* The interface is declared here (in the package entry module, not in
|
|
18
|
+
* `pageRegistry.ts`) because TypeScript module augmentation only merges with
|
|
19
|
+
* interfaces declared directly in the augmented module — an augmentation of
|
|
20
|
+
* "@qawolf/pom" would not reach an interface that is merely re-exported.
|
|
21
|
+
*/
|
|
22
|
+
export interface RegisteredPages {
|
|
23
|
+
}
|
|
24
|
+
export { type BaselineScreenshotFn, BasePageObject } from "./basePageObject.js";
|
|
25
|
+
export { type ReportCleanupFailedParams, reportCleanupFailed, reportCleanupFailure, } from "./cleanupUtils.js";
|
|
26
|
+
export { type InitializeBrowserOptions, EntryPointPageObject, } from "./entryPointPageObject.js";
|
|
27
|
+
export { type NetworkError, NetworkMonitor } from "./networkMonitor.js";
|
|
28
|
+
export type { PageSetupOptions, PopupHandlerDef, RouteInterceptorDef, } from "./pageHooks.js";
|
|
29
|
+
export { type PomModuleLoader, type RegisterPageOptions, createPage, registerPage, } from "./pageRegistry.js";
|
|
30
|
+
export { callPlatformAPI } from "./platformClient.js";
|
|
31
|
+
export { PopupHandler } from "./popupHandler.js";
|
|
32
|
+
export type { SequencePromise } from "./sequence.js";
|
|
33
|
+
export { SubPageObject } from "./subPageObject.js";
|
|
34
|
+
export { assertPricesClose, moneyToNumber, numberToMoney, } from "./testDataUtilities.js";
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,eAAe;CAAG;AAEnC,OAAO,EAAE,KAAK,oBAAoB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EACL,KAAK,yBAAyB,EAC9B,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,wBAAwB,EAC7B,oBAAoB,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACxE,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,UAAU,EACV,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { BasePageObject } from "./basePageObject.js";
|
|
2
|
+
export { reportCleanupFailed, reportCleanupFailure, } from "./cleanupUtils.js";
|
|
3
|
+
export { EntryPointPageObject, } from "./entryPointPageObject.js";
|
|
4
|
+
export { NetworkMonitor } from "./networkMonitor.js";
|
|
5
|
+
export { createPage, registerPage, } from "./pageRegistry.js";
|
|
6
|
+
export { callPlatformAPI } from "./platformClient.js";
|
|
7
|
+
export { PopupHandler } from "./popupHandler.js";
|
|
8
|
+
export { SubPageObject } from "./subPageObject.js";
|
|
9
|
+
export { assertPricesClose, moneyToNumber, numberToMoney, } from "./testDataUtilities.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAwBA,OAAO,EAA6B,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAEL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,oBAAoB,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAqB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAMxE,OAAO,EAGL,UAAU,EACV,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Page } from "playwright";
|
|
2
|
+
export type NetworkError = {
|
|
3
|
+
readonly method: string;
|
|
4
|
+
readonly resourceType: string;
|
|
5
|
+
readonly status: number;
|
|
6
|
+
readonly statusText: string;
|
|
7
|
+
readonly timestamp: number;
|
|
8
|
+
readonly url: string;
|
|
9
|
+
};
|
|
10
|
+
export declare class NetworkMonitor {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
/** Only 4xx errors. */
|
|
13
|
+
get clientErrors(): readonly NetworkError[];
|
|
14
|
+
/** All recorded 4xx/5xx errors. */
|
|
15
|
+
get errors(): readonly NetworkError[];
|
|
16
|
+
/** Only 5xx errors. */
|
|
17
|
+
get serverErrors(): readonly NetworkError[];
|
|
18
|
+
private _errors;
|
|
19
|
+
private _page;
|
|
20
|
+
constructor(name: string);
|
|
21
|
+
/** Assert zero HTTP errors were recorded, with optional URL exclusions. */
|
|
22
|
+
assertClean(options?: {
|
|
23
|
+
exclude?: RegExp[];
|
|
24
|
+
}): void;
|
|
25
|
+
/** Assert zero 5xx errors were recorded, with optional URL exclusions. */
|
|
26
|
+
assertNoServerErrors(options?: {
|
|
27
|
+
exclude?: RegExp[];
|
|
28
|
+
}): void;
|
|
29
|
+
/** Bind to a page. Throws if already bound — one monitor per page. */
|
|
30
|
+
install(page: Page): void;
|
|
31
|
+
/** Reset collected errors (useful mid-flow if only a specific section matters). */
|
|
32
|
+
reset(): void;
|
|
33
|
+
private _filtered;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=networkMonitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"networkMonitor.d.ts","sourceRoot":"","sources":["../src/networkMonitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAY,MAAM,YAAY,CAAC;AAIjD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,qBAAa,cAAc;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uBAAuB;IACvB,IAAI,YAAY,IAAI,SAAS,YAAY,EAAE,CAE1C;IACD,mCAAmC;IACnC,IAAI,MAAM,IAAI,SAAS,YAAY,EAAE,CAEpC;IAED,uBAAuB;IACvB,IAAI,YAAY,IAAI,SAAS,YAAY,EAAE,CAE1C;IAED,OAAO,CAAC,OAAO,CAAsB;IAErC,OAAO,CAAC,KAAK,CAA+B;gBAEhC,IAAI,EAAE,MAAM;IAIxB,2EAA2E;IAC3E,WAAW,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI;IAWnD,0EAA0E;IAC1E,oBAAoB,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI;IAa5D,sEAAsE;IACtE,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAyBzB,mFAAmF;IACnF,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,SAAS;CAIlB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { expect } from "@qawolf/flows/web";
|
|
2
|
+
export class NetworkMonitor {
|
|
3
|
+
name;
|
|
4
|
+
/** Only 4xx errors. */
|
|
5
|
+
get clientErrors() {
|
|
6
|
+
return this._errors.filter((e) => e.status < 500);
|
|
7
|
+
}
|
|
8
|
+
/** All recorded 4xx/5xx errors. */
|
|
9
|
+
get errors() {
|
|
10
|
+
return [...this._errors];
|
|
11
|
+
}
|
|
12
|
+
/** Only 5xx errors. */
|
|
13
|
+
get serverErrors() {
|
|
14
|
+
return this._errors.filter((e) => e.status >= 500);
|
|
15
|
+
}
|
|
16
|
+
_errors = [];
|
|
17
|
+
_page = undefined;
|
|
18
|
+
constructor(name) {
|
|
19
|
+
this.name = name;
|
|
20
|
+
}
|
|
21
|
+
/** Assert zero HTTP errors were recorded, with optional URL exclusions. */
|
|
22
|
+
assertClean(options) {
|
|
23
|
+
const errors = this._filtered(options?.exclude);
|
|
24
|
+
expect(errors, `NetworkMonitor "${this.name}" recorded ${errors.length} HTTP error(s):\n` +
|
|
25
|
+
errors
|
|
26
|
+
.map((e) => ` ${e.method} ${e.status} ${e.statusText} ${e.url}`)
|
|
27
|
+
.join("\n")).toHaveLength(0);
|
|
28
|
+
}
|
|
29
|
+
/** Assert zero 5xx errors were recorded, with optional URL exclusions. */
|
|
30
|
+
assertNoServerErrors(options) {
|
|
31
|
+
const errors = this._filtered(options?.exclude).filter((e) => e.status >= 500);
|
|
32
|
+
expect(errors, `NetworkMonitor "${this.name}" recorded ${errors.length} server error(s):\n` +
|
|
33
|
+
errors
|
|
34
|
+
.map((e) => ` ${e.method} ${e.status} ${e.statusText} ${e.url}`)
|
|
35
|
+
.join("\n")).toHaveLength(0);
|
|
36
|
+
}
|
|
37
|
+
/** Bind to a page. Throws if already bound — one monitor per page. */
|
|
38
|
+
install(page) {
|
|
39
|
+
if (this._page) {
|
|
40
|
+
throw Error(`NetworkMonitor "${this.name}" is already tracking a page. ` +
|
|
41
|
+
`Each monitor tracks exactly one page handle. For multi-page ` +
|
|
42
|
+
`sessions, create a separate NetworkMonitor per page:\n\n` +
|
|
43
|
+
` const main = new NetworkMonitor("main");\n` +
|
|
44
|
+
` const secondary = new NetworkMonitor("secondary");`);
|
|
45
|
+
}
|
|
46
|
+
this._page = page;
|
|
47
|
+
page.on("response", (response) => {
|
|
48
|
+
if (response.status() >= 400) {
|
|
49
|
+
this._errors.push({
|
|
50
|
+
method: response.request().method(),
|
|
51
|
+
resourceType: response.request().resourceType(),
|
|
52
|
+
status: response.status(),
|
|
53
|
+
statusText: response.statusText(),
|
|
54
|
+
timestamp: Date.now(),
|
|
55
|
+
url: response.url(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** Reset collected errors (useful mid-flow if only a specific section matters). */
|
|
61
|
+
reset() {
|
|
62
|
+
this._errors = [];
|
|
63
|
+
}
|
|
64
|
+
_filtered(exclude) {
|
|
65
|
+
if (!exclude?.length)
|
|
66
|
+
return [...this._errors];
|
|
67
|
+
return this._errors.filter((e) => !exclude.some((rx) => rx.test(e.url)));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=networkMonitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"networkMonitor.js","sourceRoot":"","sources":["../src/networkMonitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page-hook collection — reads `popupHandlers()` / `routeInterceptors()` defs
|
|
3
|
+
* off every registered POM for `EntryPointPageObject.installPageHooks`.
|
|
4
|
+
*
|
|
5
|
+
* Hook defs can only be read off a loaded class, so this loads every lazy
|
|
6
|
+
* registry entry except those registered `{ providesPageHooks: false }`.
|
|
7
|
+
* Overrides are detected via `Object.hasOwn(cls.prototype, ...)`, so only
|
|
8
|
+
* classes that directly declare the override are picked up.
|
|
9
|
+
*/
|
|
10
|
+
import type { Page } from "playwright";
|
|
11
|
+
import type { PopupHandlerDef, RouteInterceptorDef } from "./pageHooks.js";
|
|
12
|
+
export declare function getRegisteredPopupHandlers(page: Page): Promise<{
|
|
13
|
+
className: string;
|
|
14
|
+
defs: PopupHandlerDef[];
|
|
15
|
+
}[]>;
|
|
16
|
+
export declare function getRegisteredRouteInterceptors(page: Page): Promise<{
|
|
17
|
+
className: string;
|
|
18
|
+
defs: RouteInterceptorDef[];
|
|
19
|
+
}[]>;
|
|
20
|
+
//# sourceMappingURL=pageHookCollection.d.ts.map
|