@apica-io/asm-playwright-runner 1.0.0-dev.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/dist/runner.js ADDED
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.PlaywrightRunner = void 0;
39
+ const fs_1 = __importDefault(require("fs"));
40
+ const path_1 = __importDefault(require("path"));
41
+ const playwright_1 = require("playwright");
42
+ const actions_1 = require("./actions");
43
+ const runnerConfig_1 = require("./model/runnerConfig");
44
+ const parser_1 = require("./lib/parser");
45
+ const log4js_1 = require("log4js");
46
+ const node_child_process_1 = require("node:child_process");
47
+ const helper_1 = require("./lib/helper");
48
+ const RESULT_FILE = "trace-model.json";
49
+ class PlaywrightRunner {
50
+ constructor() {
51
+ this.browser = null;
52
+ this.context = null;
53
+ this.page = null;
54
+ this.options = null;
55
+ this.logger = (0, log4js_1.getLogger)(PlaywrightRunner.name);
56
+ }
57
+ init(collectionPath, options) {
58
+ return __awaiter(this, void 0, void 0, function* () {
59
+ var _a;
60
+ this.logger.info("Initializing PlaywrightRunner...");
61
+ // Normalize resultDir
62
+ if (!options.resultDir || options.resultDir.trim() === "") {
63
+ options.resultDir = runnerConfig_1.ResultDir.BASE_DIR;
64
+ this.logger.debug("No resultDir specified. Defaulting to 'results'.");
65
+ }
66
+ this.options = options;
67
+ // Detect script type
68
+ this.options.scriptType = yield this.detectScriptType(collectionPath);
69
+ this.options.isValidScript = this.options.scriptType !== undefined;
70
+ if (!this.options.isValidScript) {
71
+ this.logger.warn("Script is not valid.");
72
+ process.exit(9);
73
+ }
74
+ // Skip browser setup for Playwright Test files
75
+ if (this.options.scriptType === runnerConfig_1.ScriptType.PLAYWRIGHT_TEST) {
76
+ this.logger.info("Playwright Test detected. Skipping manual browser setup.");
77
+ return;
78
+ }
79
+ // Browser setup
80
+ const browserType = options.browser || runnerConfig_1.BrowserType.CHROMIUM;
81
+ const headless = options.headless;
82
+ this.logger.debug(`Browser type: ${browserType}, headless: ${headless}`);
83
+ this.browser = yield (browserType === runnerConfig_1.BrowserType.FIREFOX ? playwright_1.firefox :
84
+ browserType === runnerConfig_1.BrowserType.WEBKIT ? playwright_1.webkit :
85
+ playwright_1.chromium).launch(Object.assign({ headless }, (options.chromiumPath && { executablePath: options.chromiumPath })));
86
+ this.logger.info("Browser launched successfully.");
87
+ let headers = {};
88
+ if (options.extraHTTPHeaders) {
89
+ try {
90
+ headers = JSON.parse(options.extraHTTPHeaders);
91
+ }
92
+ catch (err) {
93
+ this.logger.warn("Invalid JSON for extraHTTPHeaders, ignoring:", options.extraHTTPHeaders);
94
+ headers = {}; // fallback to empty headers
95
+ }
96
+ }
97
+ this.context = yield this.browser.newContext(Object.assign(Object.assign({ ignoreHTTPSErrors: true }, (options.sslClientCert && {
98
+ clientCert: {
99
+ cert: options.sslClientCert,
100
+ key: options.sslClientKey,
101
+ passphrase: options.sslClientPassphrase
102
+ }
103
+ })), { extraHTTPHeaders: headers }));
104
+ this.logger.info("Browser context created.");
105
+ yield this.context.tracing.start({ snapshots: true, screenshots: true, sources: true });
106
+ this.logger.info("Tracing started (snapshots, screenshots, sources enabled).");
107
+ this.page = yield this.context.newPage();
108
+ // @ts-ignore
109
+ if (((_a = this.options) === null || _a === void 0 ? void 0 : _a.scriptType) != runnerConfig_1.ScriptType.PLAYWRIGHT_TEST && this.options.timeout != null) {
110
+ this.page.setDefaultTimeout(Number(this.options.timeout));
111
+ }
112
+ this.logger.info("New page created.");
113
+ });
114
+ }
115
+ run(collectionPath, options) {
116
+ return __awaiter(this, void 0, void 0, function* () {
117
+ var _a;
118
+ this.logger.info(`Running collection: ${collectionPath}`);
119
+ // Default trace path(s)
120
+ let tracePaths = [
121
+ options.resultDir
122
+ ? path_1.default.join(options.resultDir, "trace.zip")
123
+ : "trace.zip"
124
+ ];
125
+ switch ((_a = this.options) === null || _a === void 0 ? void 0 : _a.scriptType) {
126
+ case runnerConfig_1.ScriptType.JSON: {
127
+ const flow = (0, helper_1.loadFlow)(collectionPath);
128
+ this.logger.info(`Loaded flow: ${flow.name} with ${flow.steps.length} steps`);
129
+ for (const [index, step] of flow.steps.entries()) {
130
+ const handler = actions_1.actionRegistry[step.action];
131
+ this.logger.debug(`Executing step ${index}: action=${step.action}`);
132
+ try {
133
+ if (!handler)
134
+ this.logger.error(`Unknown action: ${step.action}`);
135
+ if (!this.page) {
136
+ this.logger.error("Page not initialized. Did you call init()?");
137
+ process.exit(9);
138
+ }
139
+ yield handler(this.page, step);
140
+ }
141
+ catch (err) {
142
+ this.logger.error(`Step ${index} failed: ${err.message}`, err);
143
+ }
144
+ }
145
+ break;
146
+ }
147
+ case runnerConfig_1.ScriptType.PLAYWRIGHT: {
148
+ this.logger.info("Detected plain Playwright script with default export.");
149
+ const mod = yield Promise.resolve(`${path_1.default.resolve(collectionPath)}`).then(s => __importStar(require(s)));
150
+ const fn = mod.default;
151
+ if (typeof fn !== "function") {
152
+ throw new Error("Script does not export a default function");
153
+ }
154
+ try {
155
+ yield fn(this.page);
156
+ this.logger.info("Script execution finished.");
157
+ }
158
+ catch (err) {
159
+ this.logger.error(`Script execution failed: ${err.message}`);
160
+ }
161
+ break;
162
+ }
163
+ case runnerConfig_1.ScriptType.PLAYWRIGHT_TEST: {
164
+ this.logger.info("Detected Playwright Test file. Running via CLI...");
165
+ const args = [
166
+ "playwright",
167
+ "test",
168
+ collectionPath,
169
+ "--browser", options.browser || runnerConfig_1.BrowserType.CHROMIUM,
170
+ "--output", options.resultDir || "results",
171
+ "--trace", "on"
172
+ ];
173
+ if (options.timeout != null)
174
+ args.push("--timeout", options.timeout);
175
+ if (!options.headless)
176
+ args.push("--headed");
177
+ // if (options.verbose) args.push("--verbose");
178
+ this.logger.debug(`Spawning Playwright CLI: npx ${args.join(" ")}`);
179
+ yield new Promise((resolve) => {
180
+ const proc = (0, node_child_process_1.spawn)("npx", args, { stdio: "inherit", shell: true });
181
+ proc.on("close", (code) => {
182
+ if (code === 0) {
183
+ this.logger.info("Playwright Test finished successfully.");
184
+ }
185
+ else {
186
+ this.logger.error(`Playwright Test exited with code ${code}`);
187
+ }
188
+ tracePaths = (0, helper_1.collectTraceFiles)(options.resultDir || "results");
189
+ resolve();
190
+ });
191
+ });
192
+ break;
193
+ }
194
+ default:
195
+ this.logger.error("Unsupported or invalid script type.");
196
+ process.exit(9);
197
+ }
198
+ this.logger.info("Stopping tracing...");
199
+ yield this.traceProcess(tracePaths);
200
+ });
201
+ }
202
+ traceProcess(tracePaths) {
203
+ return __awaiter(this, void 0, void 0, function* () {
204
+ var _a, _b, _c, _d, _e, _f, _g;
205
+ if (!(tracePaths === null || tracePaths === void 0 ? void 0 : tracePaths.length)) {
206
+ this.logger.warn("No trace paths provided.");
207
+ return;
208
+ }
209
+ // Only stop tracing if this.context is active and we’re handling a live run
210
+ if (this.context && ((_a = this.options) === null || _a === void 0 ? void 0 : _a.scriptType) != runnerConfig_1.ScriptType.PLAYWRIGHT_TEST) {
211
+ yield this.context.tracing.stop({
212
+ path: tracePaths[0]
213
+ });
214
+ this.logger.info(`Trace saved to ${tracePaths}`);
215
+ }
216
+ const models = {};
217
+ for (const tracePath of tracePaths) {
218
+ try {
219
+ this.logger.info(`Parsing trace: ${tracePath}`);
220
+ const traceModel = yield (0, parser_1.prepareTraceModel)(tracePath, (_b = this.options) === null || _b === void 0 ? void 0 : _b.logLevel);
221
+ const runName = path_1.default.basename(path_1.default.dirname(tracePath));
222
+ models[runName] = traceModel;
223
+ const individualPath = path_1.default.join(path_1.default.dirname(tracePath), RESULT_FILE);
224
+ fs_1.default.writeFileSync(individualPath, JSON.stringify({ "__self": traceModel }, null, 2), "utf-8");
225
+ this.logger.debug(`Individual trace model written to ${individualPath}`);
226
+ }
227
+ catch (err) {
228
+ this.logger.error(`Failed to process trace ${tracePath}: ${err.message}`, err);
229
+ }
230
+ }
231
+ // Write combined model if more than one trace
232
+ if (((_c = this.options) === null || _c === void 0 ? void 0 : _c.scriptType) == runnerConfig_1.ScriptType.PLAYWRIGHT_TEST) {
233
+ const combinedPath = ((_d = this.options) === null || _d === void 0 ? void 0 : _d.resultDir)
234
+ ? path_1.default.join(this.options.resultDir, RESULT_FILE)
235
+ : RESULT_FILE;
236
+ fs_1.default.writeFileSync(combinedPath, JSON.stringify(models, null, 2), "utf-8");
237
+ this.logger.info(`Combined trace model written to ${combinedPath}`);
238
+ }
239
+ const resourceDir = (_f = (_e = this.options) === null || _e === void 0 ? void 0 : _e.resultDir) !== null && _f !== void 0 ? _f : runnerConfig_1.ResultDir.BASE_DIR;
240
+ const finalZipPath = path_1.default.join(resourceDir, `artifacts.zip`);
241
+ yield (0, parser_1.zipResources)(finalZipPath, path_1.default.join(resourceDir, runnerConfig_1.ResultDir.SCREENSHOT), path_1.default.join(resourceDir, runnerConfig_1.ResultDir.SOURCE));
242
+ if ((_g = this.options) === null || _g === void 0 ? void 0 : _g.returnResult) {
243
+ console.log(JSON.stringify(models));
244
+ }
245
+ });
246
+ }
247
+ detectScriptType(collectionPath) {
248
+ return __awaiter(this, void 0, void 0, function* () {
249
+ try {
250
+ const mod = yield Promise.resolve(`${path_1.default.resolve(collectionPath)}`).then(s => __importStar(require(s)));
251
+ if (typeof mod.default === "function") {
252
+ this.logger.debug("Detected plain Playwright script (default export).");
253
+ return runnerConfig_1.ScriptType.PLAYWRIGHT;
254
+ }
255
+ }
256
+ catch (_a) {
257
+ this.logger.debug("Dynamic import failed, falling back to file content check.");
258
+ }
259
+ if (collectionPath.endsWith(".json")) {
260
+ this.logger.debug("Detected JSON flow.");
261
+ return runnerConfig_1.ScriptType.JSON;
262
+ }
263
+ const fileContent = fs_1.default.readFileSync(collectionPath, "utf-8");
264
+ if (fileContent.includes("test(")) {
265
+ this.logger.debug("Detected Playwright Test file.");
266
+ return runnerConfig_1.ScriptType.PLAYWRIGHT_TEST;
267
+ }
268
+ return undefined;
269
+ });
270
+ }
271
+ close() {
272
+ return __awaiter(this, void 0, void 0, function* () {
273
+ var _a;
274
+ this.logger.info("Closing browser...");
275
+ yield ((_a = this.browser) === null || _a === void 0 ? void 0 : _a.close());
276
+ this.logger.info("Browser closed.");
277
+ });
278
+ }
279
+ }
280
+ exports.PlaywrightRunner = PlaywrightRunner;
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@apica-io/asm-playwright-runner",
3
+ "version": "1.0.0-dev.1",
4
+ "description": "CLI wrapper for Playwright collections or scripts with dynamic actions, config, and test result models.",
5
+ "main": "dist/cli.js",
6
+ "bin": {
7
+ "asm-playwright-runner": "./dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "ts-node src/cli/ index.ts",
12
+ "dev": "ts-node-dev --respawn src/cli/index.ts",
13
+ "test:playwright-script": "ts-node src/cli samples/playwright-script.spec.ts --timeout 10000 --browser chromium --headless -v -r result1 -l debug",
14
+ "test:playwright/test-script": "ts-node src/cli samples/playwright-test-script.spec.ts --browser chromium --headless -r result2 -l debug",
15
+ "test:playwright-multi-script": "ts-node src/cli samples/playwright-multi-script.spec.ts --browser chromium --headless -v -r result3 -l debug",
16
+ "test:betsson-script": "ts-node src/cli samples/betsson.spec.ts --browser chromium -r result-betsson --headless false -l debug --extraHTTPHeaders {\\\"x-obg-bypass-fabric\\\":\\\"1\\\",\\\"x-obg-experiments\\\":\\\"b1ccAR1gW0f8\\\"}",
17
+ "test:playwright-json": "ts-node src/cli samples/example.json --browser chromium --headless -v -r results3",
18
+ "test:google": "ts-node src/cli samples/google.json --browser chromium --headless -v -r playwright-multi-script",
19
+ "test:google-firefox": "ts-node src/cli samples/google.json --browser firefox --headless -v -r playwright-multi-script"
20
+ },
21
+ "keywords": [
22
+ "playwright",
23
+ "cli",
24
+ "automation",
25
+ "testing",
26
+ "runner",
27
+ "Apica"
28
+ ],
29
+ "author": "Bhavik, Apica",
30
+ "license": "ISC",
31
+ "dependencies": {
32
+ "commander": "^11.0.0",
33
+ "playwright": "^1.60.0",
34
+ "playwright-core": "^1.59.1",
35
+ "log4js": "^6.3.0"
36
+ },
37
+ "devDependencies": {
38
+ "@playwright/test": "^1.59.1",
39
+ "@types/leaflet": "^1.9.15",
40
+ "@types/node": "^25.6.2",
41
+ "ts-node": "^10.9.2",
42
+ "ts-node-dev": "^2.0.0",
43
+ "typescript": "^5.4.0",
44
+ "unzipper": "^0.12.3",
45
+ "archiver": "^5.3.2"
46
+ }
47
+ }
@@ -0,0 +1,86 @@
1
+ import { expect, Page } from "@playwright/test";
2
+
3
+ export default async function example(page: Page) {
4
+
5
+ // Open site
6
+ await page.goto(
7
+ "https://caba.betsson.bet.ar/?source=apica",
8
+ {
9
+ waitUntil: "networkidle"
10
+ }
11
+ );
12
+
13
+ // Wait for header
14
+ await page.waitForSelector(
15
+ "site-header-version-manager"
16
+ );
17
+
18
+ // Login button inside shadow dom
19
+ const loginButton = page.locator(`
20
+ router-link-v2[data-test-id="login-button"]
21
+ >> fds-button_control
22
+ >> fds-button
23
+ >> button[data-test-id="btn-1-button"]
24
+ `);
25
+
26
+ await loginButton.waitFor({
27
+ state: 'visible',
28
+ timeout: 60000
29
+ });
30
+
31
+ await expect(loginButton).toBeVisible();
32
+
33
+ await loginButton.click();
34
+
35
+ // Email input
36
+ const emailInput = page.locator(`
37
+ site-root_default
38
+ >> fds-dialog
39
+ >> account-login_fabric_wrapper
40
+ >> account-login-fabric
41
+ >> #email-input
42
+ `);
43
+
44
+ await emailInput.waitFor({
45
+ state: 'visible',
46
+ timeout: 60000
47
+ });
48
+
49
+ await expect(emailInput).toBeVisible();
50
+
51
+ await emailInput.fill(
52
+ "noc@betssongroup.com"
53
+ );
54
+
55
+ // Password input
56
+ const passwordInput = page.locator(`
57
+ site-root_default
58
+ >> fds-dialog
59
+ >> account-login_fabric_wrapper
60
+ >> account-login-fabric
61
+ >> #password-input
62
+ `);
63
+
64
+ await expect(passwordInput).toBeVisible();
65
+
66
+ await passwordInput.fill(
67
+ "n0cT35tAcc0unt!!"
68
+ );
69
+
70
+ // Submit login
71
+ const submitButton = page.locator(`
72
+ site-root_default
73
+ >> fds-dialog
74
+ >> account-login_fabric_wrapper
75
+ >> account-login-fabric
76
+ >> fds-button_control
77
+ >> fds-button
78
+ `).last();
79
+
80
+ await expect(submitButton).toBeVisible();
81
+
82
+ await submitButton.click();
83
+
84
+ // Wait after login
85
+ await page.waitForTimeout(10000);
86
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "example",
3
+ "steps": [
4
+ { "action": "goto", "url": "https://playwright.dev/" },
5
+ { "action": "assertTitle", "expected": "Playwright" },
6
+ { "action": "click", "selector": "role=link[name='Get started']" },
7
+ { "action": "waitForSelector", "selector": "role=heading[name='Installation']" },
8
+ { "action": "assertVisible", "selector": "role=heading[name='Installation']" }
9
+ ]
10
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "example",
3
+ "steps": [
4
+ { "action": "goto", "url": "https://nghttp2.org" }
5
+ ]
6
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * To learn more about Playwright Test visit:
3
+ * https://checklyhq.com/docs/browser-checks/playwright-test/
4
+ * https://playwright.dev/docs/writing-tests
5
+ */
6
+
7
+ const { expect, test } = require('@playwright/test')
8
+
9
+ // Configure the Playwright Test timeout to 210 seconds,
10
+ // ensuring that longer tests conclude before Checkly's browser check timeout of 240 seconds.
11
+ // The default Playwright Test timeout is set at 30 seconds.
12
+ // For additional information on timeouts, visit: https://checklyhq.com/docs/browser-checks/timeouts/
13
+ test.setTimeout(210000)
14
+
15
+ // Set the action timeout to 10 seconds to quickly identify failing actions.
16
+ // By default Playwright Test has no timeout for actions (e.g. clicking an element).
17
+ test.use({ actionTimeout: 10000 })
18
+
19
+ // You can use test.describe to declare a group of related test cases
20
+ test.describe('Playwright home page', () => {
21
+ // The callback in test.beforeEach will be executed before each test.
22
+ test.beforeEach(async ({ page }) => {
23
+ // Each test will be given a new page instance navigated to the this URL
24
+ // For deployments Checkly will inject the deployment url as ENVIRONMENT_URL
25
+ await page.goto(process.env.ENVIRONMENT_URL || 'https://playwright.dev/')
26
+ })
27
+ // Other useful hooks: test.beforeAll, test.afterEach, test.afterAll
28
+
29
+ test('has a page title containing Playwright', async ({ page }) => {
30
+ // Expect a title "to contain" a substring.
31
+ await expect(page).toHaveTitle(/Playwright/)
32
+ })
33
+
34
+ test("has a 'get started' link linking to the intro page", async ({ page }) => {
35
+ // Create a locator based on a text selector.
36
+ const getStarted = page.getByText('Get Started')
37
+ // Use the locator for runtime 2022.02:
38
+ // const getStarted = page.locator('text=Get Started')
39
+
40
+ // Expect an attribute "to be strictly equal" to the value.
41
+ await expect(getStarted).toHaveAttribute('href', '/docs/intro')
42
+
43
+ // Click the get started link.
44
+ await getStarted.click()
45
+
46
+ // Expects the URL to contain intro.
47
+ await expect(page).toHaveURL(/.*intro/)
48
+ })
49
+
50
+ test.describe('has Open Graph tags', () => {
51
+ const tags = ['description', 'title', 'url']
52
+
53
+ // You can create tests from an array, by calling "test()" in a loop
54
+ tags.forEach((tag) => {
55
+ test(`has the Open Graph tag "${tag}"`, async ({ page }) => {
56
+ await expect(page.locator(`meta[property="og:${tag}"]`)).toHaveCount(1)
57
+ })
58
+ })
59
+ })
60
+ })
@@ -0,0 +1,9 @@
1
+ import {expect, Page} from "playwright/test";
2
+
3
+ export default async function example(page: Page) {
4
+ await page.goto("https://playwright.dev/");
5
+ await expect(page).toHaveTitle(/Playwright/);
6
+
7
+ await page.getByRole("link", { name: "Get started" }).click();
8
+ await expect(page.getByRole("heading", { name: "Installation" })).toBeVisible();
9
+ }
@@ -0,0 +1,18 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test('has title', async ({ page }) => {
4
+ await page.goto('https://playwright.dev/');
5
+
6
+ // Expect a title "to contain" a substring.
7
+ await expect(page).toHaveTitle(/Playwright/);
8
+ });
9
+
10
+ test('get started link', async ({ page }) => {
11
+ await page.goto('https://playwright.dev/');
12
+
13
+ // Click the get started link.
14
+ await page.getByRole('link', { name: 'Get started' }).click();
15
+
16
+ // Expects page to have a heading with the name of Installation.
17
+ await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
18
+ });