@elliemae/smoked-suite 26.2.16 → 26.2.17
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/dist/cjs/base-test/index.js +246 -0
- package/dist/cjs/index.js +2 -0
- package/dist/esm/base-test/index.js +231 -0
- package/dist/esm/index.js +2 -0
- package/dist/types/lib/base-test/index.d.ts +126 -0
- package/dist/types/lib/index.d.ts +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var base_test_exports = {};
|
|
20
|
+
__export(base_test_exports, {
|
|
21
|
+
BaseTest: () => BaseTest
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(base_test_exports);
|
|
24
|
+
var import_test = require("@playwright/test");
|
|
25
|
+
var import_auth = require("../auth/index.js");
|
|
26
|
+
var import_page_setup = require("../page-setup/index.js");
|
|
27
|
+
var import_monocartCoverage = require("../monocartCoverage/index.js");
|
|
28
|
+
const DEFAULT_NAVIGATION_TIMEOUT = 6e4;
|
|
29
|
+
const DEFAULT_ELEMENT_TIMEOUT = 5e3;
|
|
30
|
+
class BaseTest {
|
|
31
|
+
auth = new import_auth.AuthManager();
|
|
32
|
+
pageSetup = new import_page_setup.PageSetup();
|
|
33
|
+
/** Whether sessionStorage was injected via `addInitScript`. */
|
|
34
|
+
sessionInjectedViaInitScript = false;
|
|
35
|
+
/** Top-level Playwright page. Used for navigation, auth, and interaction. */
|
|
36
|
+
page;
|
|
37
|
+
/**
|
|
38
|
+
* Alias for {@link page}. Available after calling a navigation
|
|
39
|
+
* method such as {@link goToEmAdminUI}. Pass to page objects
|
|
40
|
+
* via a callback: `() => this.appFrame`.
|
|
41
|
+
*/
|
|
42
|
+
appFrame;
|
|
43
|
+
// ─── Credentials ─────────────────────────────────────────────────────────────
|
|
44
|
+
/**
|
|
45
|
+
* Return the credentials used for the encw login flow.
|
|
46
|
+
*
|
|
47
|
+
* Reads from environment variables with local-development defaults:
|
|
48
|
+
*
|
|
49
|
+
* | Variable | Default |
|
|
50
|
+
* |---------------------|---------------|
|
|
51
|
+
* | `ENCW_INSTANCE_ID` | `BE11226875` |
|
|
52
|
+
* | `ENCW_USER_ID` | `admin` |
|
|
53
|
+
* | `ENCW_PASSWORD` | `Password#!23` |
|
|
54
|
+
*
|
|
55
|
+
* Override this method to supply different credentials.
|
|
56
|
+
*/
|
|
57
|
+
getCredentials() {
|
|
58
|
+
return {
|
|
59
|
+
instanceId: process.env.ENCW_INSTANCE_ID ?? "BE11226875",
|
|
60
|
+
username: process.env.ENCW_USER_ID ?? "admin",
|
|
61
|
+
password: process.env.ENCW_PASSWORD ?? "Password#!23"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// ─── Auth orchestration ──────────────────────────────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* Ensure the browser has a valid encw session.
|
|
67
|
+
*
|
|
68
|
+
* - First test in a worker: performs the full login flow and
|
|
69
|
+
* snapshots session storage.
|
|
70
|
+
* - Subsequent tests: session is injected during
|
|
71
|
+
* {@link navigateToApp}.
|
|
72
|
+
*
|
|
73
|
+
* This method does **not** navigate to any app route.
|
|
74
|
+
*/
|
|
75
|
+
async ensureAuth() {
|
|
76
|
+
await this.pageSetup.apply(this.page);
|
|
77
|
+
if (!this.auth.hasValidAuth() && import_auth.AuthManager.loadFromFile()) {
|
|
78
|
+
const state = import_auth.AuthManager.getAuthState();
|
|
79
|
+
if (state) {
|
|
80
|
+
await this.page.addInitScript((entries) => {
|
|
81
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
82
|
+
sessionStorage.setItem(key, value);
|
|
83
|
+
}
|
|
84
|
+
}, state.sessionEntries);
|
|
85
|
+
this.sessionInjectedViaInitScript = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (this.auth.hasValidAuth()) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
await this.auth.login(this.page, this.getCredentials());
|
|
92
|
+
await this.auth.capture(this.page);
|
|
93
|
+
}
|
|
94
|
+
// ─── App navigation ────────────────────────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Navigate to an application route.
|
|
97
|
+
*
|
|
98
|
+
* Handles auth injection (for cached sessions) and page navigation.
|
|
99
|
+
* @param route - URL path to navigate to (e.g. `/admin`).
|
|
100
|
+
*/
|
|
101
|
+
async navigateToApp(route) {
|
|
102
|
+
if (this.sessionInjectedViaInitScript) {
|
|
103
|
+
await this.page.goto(route, { waitUntil: "domcontentloaded" });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (this.auth.hasValidAuth()) {
|
|
107
|
+
await this.page.goto("", { waitUntil: "domcontentloaded" });
|
|
108
|
+
await this.auth.inject(this.page);
|
|
109
|
+
}
|
|
110
|
+
await this.page.goto(route, {
|
|
111
|
+
waitUntil: "domcontentloaded"
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Log in to encw and navigate to the **em-admin-ui** app.
|
|
116
|
+
* After this method returns, {@link page} is on the admin route.
|
|
117
|
+
*/
|
|
118
|
+
async goToEmAdminUI() {
|
|
119
|
+
await this.navigateToApp("/admin");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Log in to encw and navigate to the **pipeline** app.
|
|
123
|
+
* After this method returns, {@link page} is on the pipeline route.
|
|
124
|
+
*/
|
|
125
|
+
async goToPipeline() {
|
|
126
|
+
await this.navigateToApp("/pipeline");
|
|
127
|
+
}
|
|
128
|
+
// ─── Navigation helpers ──────────────────────────────────────────────────────
|
|
129
|
+
/**
|
|
130
|
+
* Navigate to a URL path.
|
|
131
|
+
* @param path - Absolute URL path (e.g. `/admin/oneadmin/migrate`).
|
|
132
|
+
* @param options - Navigation options forwarded to Playwright.
|
|
133
|
+
*/
|
|
134
|
+
async goto(path, options = {}) {
|
|
135
|
+
const {
|
|
136
|
+
waitUntil = "domcontentloaded",
|
|
137
|
+
timeout = DEFAULT_NAVIGATION_TIMEOUT
|
|
138
|
+
} = options;
|
|
139
|
+
await this.page.goto(path, { waitUntil, timeout });
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Wait for an element to be visible on the page.
|
|
143
|
+
* @param selector - CSS selector.
|
|
144
|
+
* @param options - Timeout and expected state.
|
|
145
|
+
* @param options.timeout
|
|
146
|
+
* @param options.state
|
|
147
|
+
*/
|
|
148
|
+
async waitForElement(selector, options = {}) {
|
|
149
|
+
const { timeout = DEFAULT_ELEMENT_TIMEOUT, state = "visible" } = options;
|
|
150
|
+
await this.page.locator(selector).waitFor({ timeout, state });
|
|
151
|
+
}
|
|
152
|
+
/** Playwright's `expect` — exposed for assertions inside test methods. */
|
|
153
|
+
get expect() {
|
|
154
|
+
return import_test.expect;
|
|
155
|
+
}
|
|
156
|
+
// ─── Lifecycle hooks ─────────────────────────────────────────────────────────
|
|
157
|
+
/**
|
|
158
|
+
* Called once before all tests in a describe block.
|
|
159
|
+
* Override in a subclass to add one-time setup logic.
|
|
160
|
+
* Note: `this.page` is not available in this hook.
|
|
161
|
+
*/
|
|
162
|
+
async beforeAll() {
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Called once after all tests in a describe block.
|
|
166
|
+
* Override in a subclass to add one-time teardown logic.
|
|
167
|
+
* Note: `this.page` is not available in this hook.
|
|
168
|
+
*/
|
|
169
|
+
async afterAll() {
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Called before each test. Override in a subclass to add setup
|
|
173
|
+
* logic (e.g. `await this.goToEmAdminUI()`).
|
|
174
|
+
*/
|
|
175
|
+
async beforeEach() {
|
|
176
|
+
}
|
|
177
|
+
/** Called after each test. Override to add teardown logic. */
|
|
178
|
+
async afterEach() {
|
|
179
|
+
}
|
|
180
|
+
// ─── Test runner ─────────────────────────────────────────────────────────────
|
|
181
|
+
static toReadableTitle(methodName) {
|
|
182
|
+
const withoutPrefix = methodName.replace(/^test/, "");
|
|
183
|
+
return withoutPrefix.replace(/([A-Z])/g, " $1").toLowerCase().trim();
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Register all `test*` methods in this class with Playwright.
|
|
187
|
+
*
|
|
188
|
+
* Call once at module level after the class definition:
|
|
189
|
+
* ```ts
|
|
190
|
+
* class MySpec extends BaseTest { ... }
|
|
191
|
+
* MySpec.run();
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
static run() {
|
|
195
|
+
const TestClass = this;
|
|
196
|
+
const className = TestClass.name;
|
|
197
|
+
const proto = TestClass.prototype;
|
|
198
|
+
const testMethods = Object.getOwnPropertyNames(proto).filter(
|
|
199
|
+
(name) => name.startsWith("test") && typeof proto[name] === "function"
|
|
200
|
+
);
|
|
201
|
+
if (testMethods.length === 0) {
|
|
202
|
+
console.warn(
|
|
203
|
+
`No test methods found in ${className}. Test methods must start with 'test'.`
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
import_test.test.describe(className, () => {
|
|
208
|
+
let instance;
|
|
209
|
+
import_test.test.beforeAll(async () => {
|
|
210
|
+
(0, import_monocartCoverage.startCoverage)();
|
|
211
|
+
instance = new TestClass();
|
|
212
|
+
if (typeof instance.beforeAll === "function") {
|
|
213
|
+
await instance.beforeAll();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
import_test.test.beforeEach(async ({ page }) => {
|
|
217
|
+
instance = new TestClass();
|
|
218
|
+
instance.page = page;
|
|
219
|
+
instance.appFrame = page;
|
|
220
|
+
await instance.ensureAuth();
|
|
221
|
+
await (0, import_monocartCoverage.trackPage)(page);
|
|
222
|
+
if (typeof instance.beforeEach === "function") {
|
|
223
|
+
await instance.beforeEach();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
import_test.test.afterEach(async () => {
|
|
227
|
+
if (typeof instance.afterEach === "function") {
|
|
228
|
+
await instance.afterEach();
|
|
229
|
+
}
|
|
230
|
+
await (0, import_monocartCoverage.untrackPage)(instance.page);
|
|
231
|
+
});
|
|
232
|
+
import_test.test.afterAll(async () => {
|
|
233
|
+
await (0, import_monocartCoverage.stopCoverage)();
|
|
234
|
+
if (typeof instance.afterAll === "function") {
|
|
235
|
+
await instance.afterAll();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
for (const methodName of testMethods) {
|
|
239
|
+
const title = TestClass.toReadableTitle(methodName);
|
|
240
|
+
(0, import_test.test)(title, async () => {
|
|
241
|
+
await instance[methodName]();
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -30,6 +30,7 @@ var index_exports = {};
|
|
|
30
30
|
__export(index_exports, {
|
|
31
31
|
AuthManager: () => import_auth.AuthManager,
|
|
32
32
|
BasePage: () => import_base_page.BasePage,
|
|
33
|
+
BaseTest: () => import_base_test.BaseTest,
|
|
33
34
|
PageSetup: () => import_page_setup.PageSetup,
|
|
34
35
|
createBrowserStackConfig: () => import_browserstack.createBrowserStackConfig,
|
|
35
36
|
createPlaywrightConfig: () => import_playwright_config.createPlaywrightConfig,
|
|
@@ -38,6 +39,7 @@ __export(index_exports, {
|
|
|
38
39
|
writeBrowserStackConfig: () => import_browserstack.writeBrowserStackConfig
|
|
39
40
|
});
|
|
40
41
|
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
var import_base_test = require("./base-test/index.js");
|
|
41
43
|
var import_base_page = require("./base-page/index.js");
|
|
42
44
|
var import_auth = require("./auth/index.js");
|
|
43
45
|
var import_page_setup = require("./page-setup/index.js");
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { test, expect } from "@playwright/test";
|
|
2
|
+
import { AuthManager } from "../auth/index.js";
|
|
3
|
+
import { PageSetup } from "../page-setup/index.js";
|
|
4
|
+
import {
|
|
5
|
+
startCoverage,
|
|
6
|
+
stopCoverage,
|
|
7
|
+
trackPage,
|
|
8
|
+
untrackPage
|
|
9
|
+
} from "../monocartCoverage/index.js";
|
|
10
|
+
const DEFAULT_NAVIGATION_TIMEOUT = 6e4;
|
|
11
|
+
const DEFAULT_ELEMENT_TIMEOUT = 5e3;
|
|
12
|
+
class BaseTest {
|
|
13
|
+
auth = new AuthManager();
|
|
14
|
+
pageSetup = new PageSetup();
|
|
15
|
+
/** Whether sessionStorage was injected via `addInitScript`. */
|
|
16
|
+
sessionInjectedViaInitScript = false;
|
|
17
|
+
/** Top-level Playwright page. Used for navigation, auth, and interaction. */
|
|
18
|
+
page;
|
|
19
|
+
/**
|
|
20
|
+
* Alias for {@link page}. Available after calling a navigation
|
|
21
|
+
* method such as {@link goToEmAdminUI}. Pass to page objects
|
|
22
|
+
* via a callback: `() => this.appFrame`.
|
|
23
|
+
*/
|
|
24
|
+
appFrame;
|
|
25
|
+
// ─── Credentials ─────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Return the credentials used for the encw login flow.
|
|
28
|
+
*
|
|
29
|
+
* Reads from environment variables with local-development defaults:
|
|
30
|
+
*
|
|
31
|
+
* | Variable | Default |
|
|
32
|
+
* |---------------------|---------------|
|
|
33
|
+
* | `ENCW_INSTANCE_ID` | `BE11226875` |
|
|
34
|
+
* | `ENCW_USER_ID` | `admin` |
|
|
35
|
+
* | `ENCW_PASSWORD` | `Password#!23` |
|
|
36
|
+
*
|
|
37
|
+
* Override this method to supply different credentials.
|
|
38
|
+
*/
|
|
39
|
+
getCredentials() {
|
|
40
|
+
return {
|
|
41
|
+
instanceId: process.env.ENCW_INSTANCE_ID ?? "BE11226875",
|
|
42
|
+
username: process.env.ENCW_USER_ID ?? "admin",
|
|
43
|
+
password: process.env.ENCW_PASSWORD ?? "Password#!23"
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// ─── Auth orchestration ──────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Ensure the browser has a valid encw session.
|
|
49
|
+
*
|
|
50
|
+
* - First test in a worker: performs the full login flow and
|
|
51
|
+
* snapshots session storage.
|
|
52
|
+
* - Subsequent tests: session is injected during
|
|
53
|
+
* {@link navigateToApp}.
|
|
54
|
+
*
|
|
55
|
+
* This method does **not** navigate to any app route.
|
|
56
|
+
*/
|
|
57
|
+
async ensureAuth() {
|
|
58
|
+
await this.pageSetup.apply(this.page);
|
|
59
|
+
if (!this.auth.hasValidAuth() && AuthManager.loadFromFile()) {
|
|
60
|
+
const state = AuthManager.getAuthState();
|
|
61
|
+
if (state) {
|
|
62
|
+
await this.page.addInitScript((entries) => {
|
|
63
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
64
|
+
sessionStorage.setItem(key, value);
|
|
65
|
+
}
|
|
66
|
+
}, state.sessionEntries);
|
|
67
|
+
this.sessionInjectedViaInitScript = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (this.auth.hasValidAuth()) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
await this.auth.login(this.page, this.getCredentials());
|
|
74
|
+
await this.auth.capture(this.page);
|
|
75
|
+
}
|
|
76
|
+
// ─── App navigation ────────────────────────────────────────────────────────
|
|
77
|
+
/**
|
|
78
|
+
* Navigate to an application route.
|
|
79
|
+
*
|
|
80
|
+
* Handles auth injection (for cached sessions) and page navigation.
|
|
81
|
+
* @param route - URL path to navigate to (e.g. `/admin`).
|
|
82
|
+
*/
|
|
83
|
+
async navigateToApp(route) {
|
|
84
|
+
if (this.sessionInjectedViaInitScript) {
|
|
85
|
+
await this.page.goto(route, { waitUntil: "domcontentloaded" });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (this.auth.hasValidAuth()) {
|
|
89
|
+
await this.page.goto("", { waitUntil: "domcontentloaded" });
|
|
90
|
+
await this.auth.inject(this.page);
|
|
91
|
+
}
|
|
92
|
+
await this.page.goto(route, {
|
|
93
|
+
waitUntil: "domcontentloaded"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Log in to encw and navigate to the **em-admin-ui** app.
|
|
98
|
+
* After this method returns, {@link page} is on the admin route.
|
|
99
|
+
*/
|
|
100
|
+
async goToEmAdminUI() {
|
|
101
|
+
await this.navigateToApp("/admin");
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Log in to encw and navigate to the **pipeline** app.
|
|
105
|
+
* After this method returns, {@link page} is on the pipeline route.
|
|
106
|
+
*/
|
|
107
|
+
async goToPipeline() {
|
|
108
|
+
await this.navigateToApp("/pipeline");
|
|
109
|
+
}
|
|
110
|
+
// ─── Navigation helpers ──────────────────────────────────────────────────────
|
|
111
|
+
/**
|
|
112
|
+
* Navigate to a URL path.
|
|
113
|
+
* @param path - Absolute URL path (e.g. `/admin/oneadmin/migrate`).
|
|
114
|
+
* @param options - Navigation options forwarded to Playwright.
|
|
115
|
+
*/
|
|
116
|
+
async goto(path, options = {}) {
|
|
117
|
+
const {
|
|
118
|
+
waitUntil = "domcontentloaded",
|
|
119
|
+
timeout = DEFAULT_NAVIGATION_TIMEOUT
|
|
120
|
+
} = options;
|
|
121
|
+
await this.page.goto(path, { waitUntil, timeout });
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Wait for an element to be visible on the page.
|
|
125
|
+
* @param selector - CSS selector.
|
|
126
|
+
* @param options - Timeout and expected state.
|
|
127
|
+
* @param options.timeout
|
|
128
|
+
* @param options.state
|
|
129
|
+
*/
|
|
130
|
+
async waitForElement(selector, options = {}) {
|
|
131
|
+
const { timeout = DEFAULT_ELEMENT_TIMEOUT, state = "visible" } = options;
|
|
132
|
+
await this.page.locator(selector).waitFor({ timeout, state });
|
|
133
|
+
}
|
|
134
|
+
/** Playwright's `expect` — exposed for assertions inside test methods. */
|
|
135
|
+
get expect() {
|
|
136
|
+
return expect;
|
|
137
|
+
}
|
|
138
|
+
// ─── Lifecycle hooks ─────────────────────────────────────────────────────────
|
|
139
|
+
/**
|
|
140
|
+
* Called once before all tests in a describe block.
|
|
141
|
+
* Override in a subclass to add one-time setup logic.
|
|
142
|
+
* Note: `this.page` is not available in this hook.
|
|
143
|
+
*/
|
|
144
|
+
async beforeAll() {
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Called once after all tests in a describe block.
|
|
148
|
+
* Override in a subclass to add one-time teardown logic.
|
|
149
|
+
* Note: `this.page` is not available in this hook.
|
|
150
|
+
*/
|
|
151
|
+
async afterAll() {
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Called before each test. Override in a subclass to add setup
|
|
155
|
+
* logic (e.g. `await this.goToEmAdminUI()`).
|
|
156
|
+
*/
|
|
157
|
+
async beforeEach() {
|
|
158
|
+
}
|
|
159
|
+
/** Called after each test. Override to add teardown logic. */
|
|
160
|
+
async afterEach() {
|
|
161
|
+
}
|
|
162
|
+
// ─── Test runner ─────────────────────────────────────────────────────────────
|
|
163
|
+
static toReadableTitle(methodName) {
|
|
164
|
+
const withoutPrefix = methodName.replace(/^test/, "");
|
|
165
|
+
return withoutPrefix.replace(/([A-Z])/g, " $1").toLowerCase().trim();
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Register all `test*` methods in this class with Playwright.
|
|
169
|
+
*
|
|
170
|
+
* Call once at module level after the class definition:
|
|
171
|
+
* ```ts
|
|
172
|
+
* class MySpec extends BaseTest { ... }
|
|
173
|
+
* MySpec.run();
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
static run() {
|
|
177
|
+
const TestClass = this;
|
|
178
|
+
const className = TestClass.name;
|
|
179
|
+
const proto = TestClass.prototype;
|
|
180
|
+
const testMethods = Object.getOwnPropertyNames(proto).filter(
|
|
181
|
+
(name) => name.startsWith("test") && typeof proto[name] === "function"
|
|
182
|
+
);
|
|
183
|
+
if (testMethods.length === 0) {
|
|
184
|
+
console.warn(
|
|
185
|
+
`No test methods found in ${className}. Test methods must start with 'test'.`
|
|
186
|
+
);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
test.describe(className, () => {
|
|
190
|
+
let instance;
|
|
191
|
+
test.beforeAll(async () => {
|
|
192
|
+
startCoverage();
|
|
193
|
+
instance = new TestClass();
|
|
194
|
+
if (typeof instance.beforeAll === "function") {
|
|
195
|
+
await instance.beforeAll();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
test.beforeEach(async ({ page }) => {
|
|
199
|
+
instance = new TestClass();
|
|
200
|
+
instance.page = page;
|
|
201
|
+
instance.appFrame = page;
|
|
202
|
+
await instance.ensureAuth();
|
|
203
|
+
await trackPage(page);
|
|
204
|
+
if (typeof instance.beforeEach === "function") {
|
|
205
|
+
await instance.beforeEach();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
test.afterEach(async () => {
|
|
209
|
+
if (typeof instance.afterEach === "function") {
|
|
210
|
+
await instance.afterEach();
|
|
211
|
+
}
|
|
212
|
+
await untrackPage(instance.page);
|
|
213
|
+
});
|
|
214
|
+
test.afterAll(async () => {
|
|
215
|
+
await stopCoverage();
|
|
216
|
+
if (typeof instance.afterAll === "function") {
|
|
217
|
+
await instance.afterAll();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
for (const methodName of testMethods) {
|
|
221
|
+
const title = TestClass.toReadableTitle(methodName);
|
|
222
|
+
test(title, async () => {
|
|
223
|
+
await instance[methodName]();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
export {
|
|
230
|
+
BaseTest
|
|
231
|
+
};
|
package/dist/esm/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BaseTest } from "./base-test/index.js";
|
|
1
2
|
import { BasePage } from "./base-page/index.js";
|
|
2
3
|
import { AuthManager } from "./auth/index.js";
|
|
3
4
|
import { PageSetup } from "./page-setup/index.js";
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
export {
|
|
12
13
|
AuthManager,
|
|
13
14
|
BasePage,
|
|
15
|
+
BaseTest,
|
|
14
16
|
PageSetup,
|
|
15
17
|
createBrowserStackConfig,
|
|
16
18
|
createPlaywrightConfig,
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Page, Expect } from '@playwright/test';
|
|
2
|
+
import { Credentials, GotoOptions } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Base class for Playwright test specs that run against
|
|
5
|
+
* apps hosted inside the encw (Encompass Web) shell.
|
|
6
|
+
*
|
|
7
|
+
* **Responsibilities (delegated to focused collaborators):**
|
|
8
|
+
* - {@link AuthManager} — encw login + session-storage caching
|
|
9
|
+
* - {@link PageSetup} — init scripts, overlay suppression, route blocks
|
|
10
|
+
*
|
|
11
|
+
* **Usage pattern:**
|
|
12
|
+
* ```ts
|
|
13
|
+
* class MySpec extends BaseTest {
|
|
14
|
+
* async beforeEach() {
|
|
15
|
+
* await this.goToEmAdminUI();
|
|
16
|
+
* }
|
|
17
|
+
* async testSomething() { ... }
|
|
18
|
+
* }
|
|
19
|
+
* MySpec.run();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare class BaseTest {
|
|
23
|
+
private readonly auth;
|
|
24
|
+
private readonly pageSetup;
|
|
25
|
+
/** Whether sessionStorage was injected via `addInitScript`. */
|
|
26
|
+
private sessionInjectedViaInitScript;
|
|
27
|
+
/** Top-level Playwright page. Used for navigation, auth, and interaction. */
|
|
28
|
+
protected page: Page;
|
|
29
|
+
/**
|
|
30
|
+
* Alias for {@link page}. Available after calling a navigation
|
|
31
|
+
* method such as {@link goToEmAdminUI}. Pass to page objects
|
|
32
|
+
* via a callback: `() => this.appFrame`.
|
|
33
|
+
*/
|
|
34
|
+
protected appFrame: Page;
|
|
35
|
+
/**
|
|
36
|
+
* Return the credentials used for the encw login flow.
|
|
37
|
+
*
|
|
38
|
+
* Reads from environment variables with local-development defaults:
|
|
39
|
+
*
|
|
40
|
+
* | Variable | Default |
|
|
41
|
+
* |---------------------|---------------|
|
|
42
|
+
* | `ENCW_INSTANCE_ID` | `BE11226875` |
|
|
43
|
+
* | `ENCW_USER_ID` | `admin` |
|
|
44
|
+
* | `ENCW_PASSWORD` | `Password#!23` |
|
|
45
|
+
*
|
|
46
|
+
* Override this method to supply different credentials.
|
|
47
|
+
*/
|
|
48
|
+
protected getCredentials(): Credentials;
|
|
49
|
+
/**
|
|
50
|
+
* Ensure the browser has a valid encw session.
|
|
51
|
+
*
|
|
52
|
+
* - First test in a worker: performs the full login flow and
|
|
53
|
+
* snapshots session storage.
|
|
54
|
+
* - Subsequent tests: session is injected during
|
|
55
|
+
* {@link navigateToApp}.
|
|
56
|
+
*
|
|
57
|
+
* This method does **not** navigate to any app route.
|
|
58
|
+
*/
|
|
59
|
+
ensureAuth(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Navigate to an application route.
|
|
62
|
+
*
|
|
63
|
+
* Handles auth injection (for cached sessions) and page navigation.
|
|
64
|
+
* @param route - URL path to navigate to (e.g. `/admin`).
|
|
65
|
+
*/
|
|
66
|
+
protected navigateToApp(route: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Log in to encw and navigate to the **em-admin-ui** app.
|
|
69
|
+
* After this method returns, {@link page} is on the admin route.
|
|
70
|
+
*/
|
|
71
|
+
protected goToEmAdminUI(): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Log in to encw and navigate to the **pipeline** app.
|
|
74
|
+
* After this method returns, {@link page} is on the pipeline route.
|
|
75
|
+
*/
|
|
76
|
+
protected goToPipeline(): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Navigate to a URL path.
|
|
79
|
+
* @param path - Absolute URL path (e.g. `/admin/oneadmin/migrate`).
|
|
80
|
+
* @param options - Navigation options forwarded to Playwright.
|
|
81
|
+
*/
|
|
82
|
+
protected goto(path: string, options?: GotoOptions): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Wait for an element to be visible on the page.
|
|
85
|
+
* @param selector - CSS selector.
|
|
86
|
+
* @param options - Timeout and expected state.
|
|
87
|
+
* @param options.timeout
|
|
88
|
+
* @param options.state
|
|
89
|
+
*/
|
|
90
|
+
protected waitForElement(selector: string, options?: {
|
|
91
|
+
timeout?: number;
|
|
92
|
+
state?: 'visible' | 'attached' | 'hidden';
|
|
93
|
+
}): Promise<void>;
|
|
94
|
+
/** Playwright's `expect` — exposed for assertions inside test methods. */
|
|
95
|
+
protected get expect(): Expect;
|
|
96
|
+
/**
|
|
97
|
+
* Called once before all tests in a describe block.
|
|
98
|
+
* Override in a subclass to add one-time setup logic.
|
|
99
|
+
* Note: `this.page` is not available in this hook.
|
|
100
|
+
*/
|
|
101
|
+
beforeAll(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Called once after all tests in a describe block.
|
|
104
|
+
* Override in a subclass to add one-time teardown logic.
|
|
105
|
+
* Note: `this.page` is not available in this hook.
|
|
106
|
+
*/
|
|
107
|
+
afterAll(): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Called before each test. Override in a subclass to add setup
|
|
110
|
+
* logic (e.g. `await this.goToEmAdminUI()`).
|
|
111
|
+
*/
|
|
112
|
+
beforeEach(): Promise<void>;
|
|
113
|
+
/** Called after each test. Override to add teardown logic. */
|
|
114
|
+
afterEach(): Promise<void>;
|
|
115
|
+
private static toReadableTitle;
|
|
116
|
+
/**
|
|
117
|
+
* Register all `test*` methods in this class with Playwright.
|
|
118
|
+
*
|
|
119
|
+
* Call once at module level after the class definition:
|
|
120
|
+
* ```ts
|
|
121
|
+
* class MySpec extends BaseTest { ... }
|
|
122
|
+
* MySpec.run();
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
static run(): void;
|
|
126
|
+
}
|