@wdio/selenium-devtools 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/README.md +411 -0
- package/dist/assertPatcher.d.ts +11 -0
- package/dist/assertPatcher.js +123 -0
- package/dist/assertPatcher.js.map +1 -0
- package/dist/bidi.d.ts +6 -0
- package/dist/bidi.js +222 -0
- package/dist/bidi.js.map +1 -0
- package/dist/constants.d.ts +75 -0
- package/dist/constants.js +146 -0
- package/dist/constants.js.map +1 -0
- package/dist/driverPatcher.d.ts +4 -0
- package/dist/driverPatcher.js +256 -0
- package/dist/driverPatcher.js.map +1 -0
- package/dist/helpers/detachedBackend.d.ts +7 -0
- package/dist/helpers/detachedBackend.js +34 -0
- package/dist/helpers/detachedBackend.js.map +1 -0
- package/dist/helpers/runtime.d.ts +3 -0
- package/dist/helpers/runtime.js +47 -0
- package/dist/helpers/runtime.js.map +1 -0
- package/dist/helpers/suiteManager.d.ts +19 -0
- package/dist/helpers/suiteManager.js +131 -0
- package/dist/helpers/suiteManager.js.map +1 -0
- package/dist/helpers/testManager.d.ts +47 -0
- package/dist/helpers/testManager.js +158 -0
- package/dist/helpers/testManager.js.map +1 -0
- package/dist/helpers/utils.d.ts +26 -0
- package/dist/helpers/utils.js +187 -0
- package/dist/helpers/utils.js.map +1 -0
- package/dist/helpers/videoEncoder.d.ts +2 -0
- package/dist/helpers/videoEncoder.js +89 -0
- package/dist/helpers/videoEncoder.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +801 -0
- package/dist/index.js.map +1 -0
- package/dist/reporter.d.ts +18 -0
- package/dist/reporter.js +72 -0
- package/dist/reporter.js.map +1 -0
- package/dist/rerunManager.d.ts +8 -0
- package/dist/rerunManager.js +78 -0
- package/dist/rerunManager.js.map +1 -0
- package/dist/runnerHooks.d.ts +6 -0
- package/dist/runnerHooks.js +594 -0
- package/dist/runnerHooks.js.map +1 -0
- package/dist/screencast.d.ts +11 -0
- package/dist/screencast.js +179 -0
- package/dist/screencast.js.map +1 -0
- package/dist/session.d.ts +48 -0
- package/dist/session.js +480 -0
- package/dist/session.js.map +1 -0
- package/dist/setupConsole.d.ts +1 -0
- package/dist/setupConsole.js +13 -0
- package/dist/setupConsole.js.map +1 -0
- package/dist/types.d.ts +235 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import logger from '@wdio/logger';
|
|
2
|
+
import { DEFAULTS, TEST_STATE } from '../constants.js';
|
|
3
|
+
import { deterministicUid, generateStableUid } from './utils.js';
|
|
4
|
+
const log = logger('@wdio/selenium-devtools:testManager');
|
|
5
|
+
/**
|
|
6
|
+
* Tracks the currently-active test inside the session suite. Two modes:
|
|
7
|
+
* - `session` (default): one synthetic test wraps the entire driver session.
|
|
8
|
+
* - `marked`: user calls startTest(name)/endTest(state); each pair adds a test.
|
|
9
|
+
*
|
|
10
|
+
* The proxy reads `getCurrentTest()` to tag each captured command with a uid.
|
|
11
|
+
*/
|
|
12
|
+
export class TestManager {
|
|
13
|
+
rootSuite;
|
|
14
|
+
testReporter;
|
|
15
|
+
suiteManager;
|
|
16
|
+
#currentTest = null;
|
|
17
|
+
#lastMarkedTest = null;
|
|
18
|
+
#mode = 'session';
|
|
19
|
+
/** Set true the first time the user calls startMarkedTest. Once true we
|
|
20
|
+
* never auto-create the synthetic session test — orphan commands attach
|
|
21
|
+
* to the most-recently-marked test instead. */
|
|
22
|
+
#userTookOver = false;
|
|
23
|
+
constructor(rootSuite, testReporter, suiteManager) {
|
|
24
|
+
this.rootSuite = rootSuite;
|
|
25
|
+
this.testReporter = testReporter;
|
|
26
|
+
this.suiteManager = suiteManager;
|
|
27
|
+
}
|
|
28
|
+
/** Where new tests attach — current scenario sub-suite (Cucumber) or root. */
|
|
29
|
+
get suite() {
|
|
30
|
+
return this.suiteManager.getCurrentParent() ?? this.rootSuite;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the test that captured commands should attach to. Order:
|
|
34
|
+
* 1. The currently-running marked test, if any.
|
|
35
|
+
* 2. (only if the user has NOT yet used startMarkedTest) the synthetic
|
|
36
|
+
* session-wide test, lazily created on first command.
|
|
37
|
+
* 3. The most-recently-ended marked test (handles commands that fire
|
|
38
|
+
* between the user's it() blocks — chromedriver retries, hooks, etc).
|
|
39
|
+
*/
|
|
40
|
+
getOrEnsureTest() {
|
|
41
|
+
if (this.#currentTest) {
|
|
42
|
+
return this.#currentTest;
|
|
43
|
+
}
|
|
44
|
+
if (!this.#userTookOver) {
|
|
45
|
+
return this.#ensureSessionTest();
|
|
46
|
+
}
|
|
47
|
+
return this.#lastMarkedTest;
|
|
48
|
+
}
|
|
49
|
+
/** Lazily creates the synthetic session-wide test on first command. */
|
|
50
|
+
#ensureSessionTest() {
|
|
51
|
+
if (this.#currentTest && this.#mode === 'session') {
|
|
52
|
+
return this.#currentTest;
|
|
53
|
+
}
|
|
54
|
+
log.info('Creating synthetic session test (no startTest called yet)');
|
|
55
|
+
const title = DEFAULTS.SESSION_TITLE;
|
|
56
|
+
const test = {
|
|
57
|
+
uid: deterministicUid(this.suite.file, `session:${this.suite.uid}`),
|
|
58
|
+
cid: DEFAULTS.CID,
|
|
59
|
+
title,
|
|
60
|
+
fullTitle: title,
|
|
61
|
+
parent: this.suite.uid,
|
|
62
|
+
state: TEST_STATE.RUNNING,
|
|
63
|
+
start: new Date(),
|
|
64
|
+
end: null,
|
|
65
|
+
type: 'test',
|
|
66
|
+
file: this.suite.file,
|
|
67
|
+
retries: DEFAULTS.RETRIES,
|
|
68
|
+
_duration: DEFAULTS.DURATION,
|
|
69
|
+
hooks: []
|
|
70
|
+
};
|
|
71
|
+
this.suite.tests.push(test);
|
|
72
|
+
this.#currentTest = test;
|
|
73
|
+
this.testReporter.onTestStart(test);
|
|
74
|
+
return test;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Public alias retained for callers that want to force the synthetic test
|
|
78
|
+
* to exist. The internal code path uses `getOrEnsureTest()` instead.
|
|
79
|
+
*/
|
|
80
|
+
ensureSessionTest() {
|
|
81
|
+
return this.#ensureSessionTest();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Switch into marked mode and start a new test. The first time this is
|
|
85
|
+
* called, any pre-existing synthetic session test is removed from the suite
|
|
86
|
+
* (along with any commands that referenced it) — once the user takes over
|
|
87
|
+
* the test boundaries, the synthetic just adds noise.
|
|
88
|
+
*/
|
|
89
|
+
startMarkedTest(name, opts = {}) {
|
|
90
|
+
if (!this.#userTookOver) {
|
|
91
|
+
this.#userTookOver = true;
|
|
92
|
+
// Drop the synthetic session test if it was lazy-created during the
|
|
93
|
+
// gap between driver creation and the user's first startTest. Any
|
|
94
|
+
// commands captured against it stay on disk in the worker buffer but
|
|
95
|
+
// are no longer reachable from the suite tree — cleaner UI.
|
|
96
|
+
if (this.#currentTest && this.#mode === 'session') {
|
|
97
|
+
log.info('Removing synthetic session test (user has taken over)');
|
|
98
|
+
const idx = this.suite.tests.indexOf(this.#currentTest);
|
|
99
|
+
if (idx !== -1) {
|
|
100
|
+
this.suite.tests.splice(idx, 1);
|
|
101
|
+
}
|
|
102
|
+
this.#currentTest = null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (this.#mode === 'marked' && this.#currentTest) {
|
|
106
|
+
this.endCurrent('passed');
|
|
107
|
+
}
|
|
108
|
+
this.#mode = 'marked';
|
|
109
|
+
const file = opts.file || this.suite.file;
|
|
110
|
+
const test = {
|
|
111
|
+
// Scope by parent so two suites with the same test/step name don't
|
|
112
|
+
// collide on signatureCounter disambiguation across rerun processes.
|
|
113
|
+
uid: generateStableUid(file, `${this.suite.uid}::${name}`),
|
|
114
|
+
cid: DEFAULTS.CID,
|
|
115
|
+
title: name,
|
|
116
|
+
fullTitle: name,
|
|
117
|
+
parent: this.suite.uid,
|
|
118
|
+
state: TEST_STATE.RUNNING,
|
|
119
|
+
start: new Date(),
|
|
120
|
+
end: null,
|
|
121
|
+
type: 'test',
|
|
122
|
+
file,
|
|
123
|
+
retries: DEFAULTS.RETRIES,
|
|
124
|
+
_duration: DEFAULTS.DURATION,
|
|
125
|
+
hooks: [],
|
|
126
|
+
callSource: opts.callSource
|
|
127
|
+
};
|
|
128
|
+
log.info(`Started marked test "${name}" (callSource: ${opts.callSource || 'n/a'})`);
|
|
129
|
+
this.suite.tests.push(test);
|
|
130
|
+
this.#currentTest = test;
|
|
131
|
+
this.#lastMarkedTest = test;
|
|
132
|
+
this.testReporter.onTestStart(test);
|
|
133
|
+
return test;
|
|
134
|
+
}
|
|
135
|
+
endCurrent(state) {
|
|
136
|
+
const test = this.#currentTest;
|
|
137
|
+
if (!test) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
test.state = state;
|
|
141
|
+
test.end = new Date();
|
|
142
|
+
test._duration = test.end.getTime() - (test.start?.getTime() ?? Date.now());
|
|
143
|
+
this.testReporter.onTestEnd(test);
|
|
144
|
+
this.#currentTest = null;
|
|
145
|
+
}
|
|
146
|
+
getCurrentTest() {
|
|
147
|
+
return this.#currentTest;
|
|
148
|
+
}
|
|
149
|
+
/** Called when the driver session is closing (process exit / quit). */
|
|
150
|
+
finalizeSession() {
|
|
151
|
+
if (this.#currentTest) {
|
|
152
|
+
this.endCurrent(this.#currentTest.state === TEST_STATE.RUNNING
|
|
153
|
+
? 'passed'
|
|
154
|
+
: this.#currentTest.state);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=testManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testManager.js","sourceRoot":"","sources":["../../src/helpers/testManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ConsoleLog, LogLevel } from '../types.js';
|
|
2
|
+
export declare const stripAnsiCodes: (text: string) => string;
|
|
3
|
+
export declare function detectLogLevel(text: string): LogLevel;
|
|
4
|
+
export declare function createConsoleLogEntry(type: LogLevel, args: any[], source?: string): ConsoleLog;
|
|
5
|
+
export declare function chromeLogLevelToLogLevel(level: string | {
|
|
6
|
+
value?: number;
|
|
7
|
+
name?: string;
|
|
8
|
+
}): LogLevel;
|
|
9
|
+
export declare function generateStableUid(file: string, name: string): string;
|
|
10
|
+
export declare function deterministicUid(...parts: string[]): string;
|
|
11
|
+
export declare function resetSignatureCounters(): void;
|
|
12
|
+
export declare function getCallSourceFromStack(): {
|
|
13
|
+
filePath: string | undefined;
|
|
14
|
+
callSource: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function findTestLineInFile(filePath: string, title: string, kind?: 'test' | 'suite'): number | null;
|
|
17
|
+
export declare function isPortInUse(port: number, hostname: string): Promise<boolean>;
|
|
18
|
+
export declare function findFreePort(startPort: number, hostname: string): Promise<number>;
|
|
19
|
+
/**
|
|
20
|
+
* Capture the command line that launched the current process so the UI's
|
|
21
|
+
* "rerun" button can re-execute the same script. Falls back to the raw
|
|
22
|
+
* argv when npm script context is unavailable.
|
|
23
|
+
*/
|
|
24
|
+
/** Derive a human-readable request type from URL and MIME type. */
|
|
25
|
+
export declare function getRequestType(url: string, mimeType?: string): string;
|
|
26
|
+
export declare function captureLaunchCommand(): string;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as net from 'node:net';
|
|
2
|
+
import { parse as parseStackTrace } from 'stacktrace-parser';
|
|
3
|
+
import logger from '@wdio/logger';
|
|
4
|
+
import { ANSI_REGEX, LOG_LEVEL_PATTERNS, LOG_SOURCES } from '../constants.js';
|
|
5
|
+
const log = logger('@wdio/selenium-devtools:utils');
|
|
6
|
+
export const stripAnsiCodes = (text) => text.replace(ANSI_REGEX, '');
|
|
7
|
+
export function detectLogLevel(text) {
|
|
8
|
+
const normalised = stripAnsiCodes(text).toLowerCase();
|
|
9
|
+
for (const { level, pattern } of LOG_LEVEL_PATTERNS) {
|
|
10
|
+
if (pattern.test(normalised)) {
|
|
11
|
+
return level;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return 'log';
|
|
15
|
+
}
|
|
16
|
+
export function createConsoleLogEntry(type, args, source = LOG_SOURCES.TEST) {
|
|
17
|
+
return { timestamp: Date.now(), type, args, source };
|
|
18
|
+
}
|
|
19
|
+
export function chromeLogLevelToLogLevel(level) {
|
|
20
|
+
const levelName = (typeof level === 'object' ? (level?.name ?? '') : (level ?? '')).toUpperCase();
|
|
21
|
+
switch (levelName) {
|
|
22
|
+
case 'SEVERE':
|
|
23
|
+
return 'error';
|
|
24
|
+
case 'WARNING':
|
|
25
|
+
return 'warn';
|
|
26
|
+
case 'INFO':
|
|
27
|
+
return 'info';
|
|
28
|
+
case 'DEBUG':
|
|
29
|
+
return 'debug';
|
|
30
|
+
default:
|
|
31
|
+
return 'log';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const signatureCounters = new Map();
|
|
35
|
+
export function generateStableUid(file, name) {
|
|
36
|
+
const signature = `${file}::${name}`;
|
|
37
|
+
const count = signatureCounters.get(signature) || 0;
|
|
38
|
+
signatureCounters.set(signature, count + 1);
|
|
39
|
+
const hashInput = count > 0 ? `${signature}::${count}` : signature;
|
|
40
|
+
const hash = hashInput
|
|
41
|
+
.split('')
|
|
42
|
+
.reduce((acc, char) => ((acc << 5) - acc + char.charCodeAt(0)) | 0, 0);
|
|
43
|
+
return `stable-${Math.abs(hash).toString(36)}`;
|
|
44
|
+
}
|
|
45
|
+
export function deterministicUid(...parts) {
|
|
46
|
+
const hash = parts
|
|
47
|
+
.join('::')
|
|
48
|
+
.split('')
|
|
49
|
+
.reduce((acc, char) => ((acc << 5) - acc + char.charCodeAt(0)) | 0, 0);
|
|
50
|
+
return `stable-${Math.abs(hash).toString(36)}`;
|
|
51
|
+
}
|
|
52
|
+
export function resetSignatureCounters() {
|
|
53
|
+
signatureCounters.clear();
|
|
54
|
+
}
|
|
55
|
+
function isUserCodeFrame(frame) {
|
|
56
|
+
const { file } = frame;
|
|
57
|
+
return !!(file &&
|
|
58
|
+
!file.includes('/node_modules/') &&
|
|
59
|
+
!file.includes('<anonymous>') &&
|
|
60
|
+
!file.includes('node:internal') &&
|
|
61
|
+
!file.includes('/dist/') &&
|
|
62
|
+
!file.endsWith('/index.js'));
|
|
63
|
+
}
|
|
64
|
+
function normalizeFilePath(filePath) {
|
|
65
|
+
// Node's stack traces in ESM use file:// URLs, which URL-encode spaces and
|
|
66
|
+
// other characters. Strip the prefix, drop the line:col suffix, and decode
|
|
67
|
+
// — otherwise `fs.readFile` hits ENOENT on any path containing a space.
|
|
68
|
+
const stripped = filePath.replace(/^file:\/\//, '').split(':')[0];
|
|
69
|
+
try {
|
|
70
|
+
return decodeURIComponent(stripped);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Malformed percent-encoding — keep the literal path rather than throw.
|
|
74
|
+
return stripped;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function getCallSourceFromStack() {
|
|
78
|
+
const stack = new Error().stack;
|
|
79
|
+
if (!stack) {
|
|
80
|
+
return { filePath: undefined, callSource: 'unknown:0' };
|
|
81
|
+
}
|
|
82
|
+
const frame = parseStackTrace(stack).find(isUserCodeFrame);
|
|
83
|
+
if (!frame?.file) {
|
|
84
|
+
return { filePath: undefined, callSource: 'unknown:0' };
|
|
85
|
+
}
|
|
86
|
+
const filePath = normalizeFilePath(frame.file);
|
|
87
|
+
return { filePath, callSource: `${filePath}:${frame.lineNumber ?? 0}` };
|
|
88
|
+
}
|
|
89
|
+
// Source-scan for `it/test/specify('title', ...)` (or `describe/context/suite`
|
|
90
|
+
// when kind='suite'). Stack-walking from inside the runner's beforeEach
|
|
91
|
+
// hooks doesn't reach the user's test body.
|
|
92
|
+
import * as fs from 'node:fs';
|
|
93
|
+
export function findTestLineInFile(filePath, title, kind = 'test') {
|
|
94
|
+
try {
|
|
95
|
+
if (!fs.existsSync(filePath)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const lines = fs.readFileSync(filePath, 'utf-8').split('\n');
|
|
99
|
+
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
100
|
+
const keywords = kind === 'suite' ? 'describe|context|suite' : 'it|test|specify';
|
|
101
|
+
const re = new RegExp(`\\b(?:${keywords})\\s*\\(\\s*['"\`]${escaped}['"\`]`);
|
|
102
|
+
for (let i = 0; i < lines.length; i++) {
|
|
103
|
+
if (re.test(lines[i])) {
|
|
104
|
+
return i + 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
/* ignore — fall back to file:0 */
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
export function isPortInUse(port, hostname) {
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
const server = net.createServer();
|
|
116
|
+
server.once('error', () => resolve(true));
|
|
117
|
+
server.once('listening', () => server.close(() => resolve(false)));
|
|
118
|
+
server.listen(port, hostname);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
export async function findFreePort(startPort, hostname) {
|
|
122
|
+
let port = startPort;
|
|
123
|
+
while (await isPortInUse(port, hostname)) {
|
|
124
|
+
log.warn(`Port ${port} is in use, trying ${port + 1}...`);
|
|
125
|
+
port++;
|
|
126
|
+
}
|
|
127
|
+
return port;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Capture the command line that launched the current process so the UI's
|
|
131
|
+
* "rerun" button can re-execute the same script. Falls back to the raw
|
|
132
|
+
* argv when npm script context is unavailable.
|
|
133
|
+
*/
|
|
134
|
+
/** Derive a human-readable request type from URL and MIME type. */
|
|
135
|
+
export function getRequestType(url, mimeType) {
|
|
136
|
+
const contentType = mimeType?.toLowerCase() ?? '';
|
|
137
|
+
const urlLower = url.toLowerCase();
|
|
138
|
+
if (contentType.includes('text/html')) {
|
|
139
|
+
return 'document';
|
|
140
|
+
}
|
|
141
|
+
if (contentType.includes('text/css')) {
|
|
142
|
+
return 'stylesheet';
|
|
143
|
+
}
|
|
144
|
+
if (contentType.includes('javascript') ||
|
|
145
|
+
contentType.includes('ecmascript')) {
|
|
146
|
+
return 'script';
|
|
147
|
+
}
|
|
148
|
+
if (contentType.includes('image/')) {
|
|
149
|
+
return 'image';
|
|
150
|
+
}
|
|
151
|
+
if (contentType.includes('font/') || contentType.includes('woff')) {
|
|
152
|
+
return 'font';
|
|
153
|
+
}
|
|
154
|
+
if (contentType.includes('application/json')) {
|
|
155
|
+
return 'fetch';
|
|
156
|
+
}
|
|
157
|
+
if (urlLower.endsWith('.html') || urlLower.endsWith('.htm')) {
|
|
158
|
+
return 'document';
|
|
159
|
+
}
|
|
160
|
+
if (urlLower.endsWith('.css')) {
|
|
161
|
+
return 'stylesheet';
|
|
162
|
+
}
|
|
163
|
+
if (urlLower.endsWith('.js') || urlLower.endsWith('.mjs')) {
|
|
164
|
+
return 'script';
|
|
165
|
+
}
|
|
166
|
+
if (/\.(png|jpg|jpeg|gif|svg|webp|ico)$/.test(urlLower)) {
|
|
167
|
+
return 'image';
|
|
168
|
+
}
|
|
169
|
+
if (/\.(woff|woff2|ttf|eot|otf)$/.test(urlLower)) {
|
|
170
|
+
return 'font';
|
|
171
|
+
}
|
|
172
|
+
return 'xhr';
|
|
173
|
+
}
|
|
174
|
+
export function captureLaunchCommand() {
|
|
175
|
+
const npmScript = process.env.npm_lifecycle_event;
|
|
176
|
+
const npmConfigUserAgent = process.env.npm_config_user_agent ?? '';
|
|
177
|
+
if (npmScript) {
|
|
178
|
+
const tool = npmConfigUserAgent.startsWith('pnpm')
|
|
179
|
+
? 'pnpm'
|
|
180
|
+
: npmConfigUserAgent.startsWith('yarn')
|
|
181
|
+
? 'yarn'
|
|
182
|
+
: 'npm';
|
|
183
|
+
return tool === 'npm' ? `npm run ${npmScript}` : `${tool} ${npmScript}`;
|
|
184
|
+
}
|
|
185
|
+
return [process.argv0, ...process.argv.slice(1)].join(' ');
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/helpers/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// VP8/WebM encoder for screencast frames.
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import logger from '@wdio/logger';
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const log = logger('@wdio/selenium-devtools:VideoEncoder');
|
|
9
|
+
export async function encodeToVideo(frames, outputPath, options = {}) {
|
|
10
|
+
if (frames.length === 0) {
|
|
11
|
+
throw new Error('VideoEncoder: no frames to encode');
|
|
12
|
+
}
|
|
13
|
+
const span = frames[frames.length - 1].timestamp - frames[0].timestamp;
|
|
14
|
+
const totalBytes = frames.reduce((sum, f) => sum + Math.floor((f.data?.length ?? 0) * 0.75), 0);
|
|
15
|
+
log.info(`🎬 Encoding ${frames.length} frame(s), captured over ${(span / 1000).toFixed(1)}s ` +
|
|
16
|
+
`(~${(totalBytes / 1024 / 1024).toFixed(1)} MB raw)`);
|
|
17
|
+
let ffmpeg;
|
|
18
|
+
try {
|
|
19
|
+
ffmpeg = require('fluent-ffmpeg');
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new Error('VideoEncoder: fluent-ffmpeg is required for screencast encoding. ' +
|
|
23
|
+
'Install it with: npm install fluent-ffmpeg');
|
|
24
|
+
}
|
|
25
|
+
const ext = options.captureFormat === 'png' ? 'png' : 'jpg';
|
|
26
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'selenium-devtools-screencast-'));
|
|
27
|
+
try {
|
|
28
|
+
const manifestLines = ['ffconcat version 1.0'];
|
|
29
|
+
for (let i = 0; i < frames.length; i++) {
|
|
30
|
+
const frameName = `frame-${String(i).padStart(6, '0')}.${ext}`;
|
|
31
|
+
const framePath = path.join(tmpDir, frameName);
|
|
32
|
+
await fs.writeFile(framePath, Buffer.from(frames[i].data, 'base64'));
|
|
33
|
+
const nextTs = frames[i + 1]?.timestamp ?? frames[i].timestamp + 100;
|
|
34
|
+
const durationSecs = Math.max((nextTs - frames[i].timestamp) / 1000, 0.01);
|
|
35
|
+
manifestLines.push(`file '${framePath}'`);
|
|
36
|
+
manifestLines.push(`duration ${durationSecs.toFixed(6)}`);
|
|
37
|
+
}
|
|
38
|
+
const lastFramePath = path.join(tmpDir, `frame-${String(frames.length - 1).padStart(6, '0')}.${ext}`);
|
|
39
|
+
manifestLines.push(`file '${lastFramePath}'`);
|
|
40
|
+
const manifestPath = path.join(tmpDir, 'manifest.txt');
|
|
41
|
+
await fs.writeFile(manifestPath, manifestLines.join('\n'));
|
|
42
|
+
log.info(`encoding ${frames.length} frames → ${outputPath}`);
|
|
43
|
+
await new Promise((resolve, reject) => {
|
|
44
|
+
ffmpeg()
|
|
45
|
+
.input(manifestPath)
|
|
46
|
+
.inputOptions(['-f', 'concat', '-safe', '0'])
|
|
47
|
+
.videoCodec('libvpx')
|
|
48
|
+
.outputOptions([
|
|
49
|
+
'-b:v',
|
|
50
|
+
'1M',
|
|
51
|
+
'-pix_fmt',
|
|
52
|
+
'yuv420p',
|
|
53
|
+
// CFR @ 10fps — VFR WebMs don't write Cues reliably, so the
|
|
54
|
+
// dashboard's <video> can't read duration/seek.
|
|
55
|
+
'-vsync',
|
|
56
|
+
'cfr',
|
|
57
|
+
'-r',
|
|
58
|
+
'10',
|
|
59
|
+
'-auto-alt-ref',
|
|
60
|
+
'0',
|
|
61
|
+
'-disposition:v',
|
|
62
|
+
'default'
|
|
63
|
+
])
|
|
64
|
+
.output(outputPath)
|
|
65
|
+
.on('end', () => resolve())
|
|
66
|
+
.on('error', (err) => {
|
|
67
|
+
const msg = err.message || '';
|
|
68
|
+
if (msg.includes('Cannot find ffmpeg') ||
|
|
69
|
+
msg.includes('ENOENT') ||
|
|
70
|
+
msg.includes('spawn') ||
|
|
71
|
+
msg.includes('not found')) {
|
|
72
|
+
reject(new Error('VideoEncoder: ffmpeg binary not found on PATH. ' +
|
|
73
|
+
'Install ffmpeg: https://ffmpeg.org/download.html'));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
reject(new Error(`VideoEncoder: ffmpeg error — ${msg}`));
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
.run();
|
|
80
|
+
});
|
|
81
|
+
log.info(`✓ video saved: ${outputPath}`);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await fs.rm(tmpDir, { recursive: true, force: true }).catch((rmErr) => {
|
|
85
|
+
log.warn(`failed to clean temp dir — ${rmErr.message}`);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=videoEncoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"videoEncoder.js","sourceRoot":"","sources":["../../src/helpers/videoEncoder.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import './setupConsole.js';
|
|
2
|
+
import { TEST_STATE, NAVIGATION_COMMANDS } from './constants.js';
|
|
3
|
+
import { type DevToolsOptions, type TestStats } from './types.js';
|
|
4
|
+
export declare const DevTools: {
|
|
5
|
+
configure: (opts: {
|
|
6
|
+
rerunCommand?: string;
|
|
7
|
+
}) => void;
|
|
8
|
+
startTest: (name: string, meta?: {
|
|
9
|
+
file?: string;
|
|
10
|
+
}) => void;
|
|
11
|
+
endTest: (state?: TestStats["state"]) => void;
|
|
12
|
+
};
|
|
13
|
+
export default DevTools;
|
|
14
|
+
export { TEST_STATE, NAVIGATION_COMMANDS };
|
|
15
|
+
export type { DevToolsOptions, TestStats };
|