@xylentis/testgen 0.0.0-stage → 1.1.0

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.
@@ -0,0 +1,388 @@
1
+ import { devices } from 'playwright-core';
2
+ import { isRegex, parseLocator } from '../locator.js';
3
+ import { pascalCase, snakeCase } from '../naming.js';
4
+ import { PageAliases, isBlankUrl, shortcutOf, signalsOf, toKeyboardModifiers } from '../recording.js';
5
+ import { CodeWriter, doubleQuote } from './writer.js';
6
+ const playwrightGoModule = 'github.com/mxschmitt/playwright-go';
7
+ // Roles with a playwright.AriaRole<Name> constant in playwright-go.
8
+ const ariaRoles = new Set(('alert alertdialog application article banner blockquote button caption cell checkbox code ' +
9
+ 'columnheader combobox complementary contentinfo definition deletion dialog directory document emphasis feed figure ' +
10
+ 'form generic grid gridcell group heading img insertion link list listbox listitem log main marquee math meter menu ' +
11
+ 'menubar menuitem menuitemcheckbox menuitemradio navigation none note option paragraph presentation progressbar radio ' +
12
+ 'radiogroup region row rowgroup rowheader scrollbar search searchbox separator slider spinbutton status strong ' +
13
+ 'subscript superscript switch tab table tablist tabpanel term textbox time timer toolbar tooltip tree treegrid treeitem').split(' '));
14
+ const capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);
15
+ export const playwrightGo = {
16
+ id: 'playwright-go',
17
+ framework: 'Playwright for Go (playwright-go)',
18
+ language: 'Go',
19
+ highlight: 'go',
20
+ testFileName: name => `${snakeCase(name)}_test.go`,
21
+ generate(recording, options) {
22
+ const imports = new Set(['os', 'testing']);
23
+ const locators = new GoLocators(() => imports.add('regexp'));
24
+ const body = new CodeWriter('\t');
25
+ const aliases = new PageAliases();
26
+ const declared = new Set();
27
+ const dialogHandlers = new Set();
28
+ let usesExpect = false;
29
+ // Whether a page is referenced by any action after `index` (Go rejects unused variables).
30
+ const usedAfter = (pageGuid, index) => recording.actions.slice(index + 1).some(a => a.pageGuid === pageGuid);
31
+ const declarePage = (alias, used) => {
32
+ declared.add(alias);
33
+ body.line(`${used ? alias : '_'}, err ${used ? ':=' : '='} context.NewPage()`);
34
+ body.line('require.NoError(t, err)');
35
+ };
36
+ recording.actions.forEach((actionInContext, index) => {
37
+ const { action, pageGuid } = actionInContext;
38
+ const pageAlias = aliases.alias(pageGuid);
39
+ if (action.name === 'openPage') {
40
+ const navigates = !isBlankUrl(action.url);
41
+ declarePage(pageAlias, navigates || usedAfter(pageGuid, index));
42
+ if (navigates)
43
+ emit(body, { expr: `${pageAlias}.Goto(${doubleQuote(action.url)})`, returnsValue: true });
44
+ return;
45
+ }
46
+ if (!declared.has(pageAlias))
47
+ declarePage(pageAlias, true);
48
+ // The browser is closed when the test ends; only pages opened later are closed explicitly.
49
+ if (action.name === 'closePage') {
50
+ if (pageAlias !== 'page')
51
+ emit(body, { expr: `${pageAlias}.Close()`, returnsValue: false });
52
+ return;
53
+ }
54
+ const signals = signalsOf(actionInContext);
55
+ if (signals.dialog && !dialogHandlers.has(pageAlias)) {
56
+ // playwright-go has no once('dialog'); the handler stays for later dialogs of this page.
57
+ dialogHandlers.add(pageAlias);
58
+ body.block(`${pageAlias}.OnDialog(func(dialog playwright.Dialog) {`, () => {
59
+ body.line('t.Logf("Dialog message: %s", dialog.Message())');
60
+ body.line('_ = dialog.Dismiss()');
61
+ }, '})');
62
+ }
63
+ const call = actionCall(pageAlias, action, locators);
64
+ if (action.name.startsWith('assert'))
65
+ usesExpect = true;
66
+ if (call.todo)
67
+ body.line(`// TODO(testgen): ${call.todo}`);
68
+ let wrapped = call;
69
+ if (signals.download)
70
+ wrapped = { expr: `${pageAlias}.ExpectDownload(${callback(wrapped)})`, returnsValue: true };
71
+ if (signals.popup) {
72
+ const popupAlias = aliases.alias(signals.popup.popupPageGuid);
73
+ const used = usedAfter(signals.popup.popupPageGuid, index);
74
+ if (used)
75
+ declared.add(popupAlias);
76
+ body.line(`${used ? popupAlias : '_'}, err ${used ? ':=' : '='} ${pageAlias}.ExpectPopup(${callback(wrapped)})`);
77
+ body.line('require.NoError(t, err)');
78
+ }
79
+ else {
80
+ emit(body, wrapped);
81
+ }
82
+ if (recording.header.generateExpectSignal && signals.expect) {
83
+ usesExpect = true;
84
+ emit(body, { expr: `expect.Locator(${locators.render(pageAlias, parseLocator(signals.expect.selector))}).ToBeVisible()`, returnsValue: false });
85
+ }
86
+ });
87
+ if (!declared.size && !recording.header.saveStorage)
88
+ body.line('_ = context');
89
+ if (recording.header.saveStorage)
90
+ emit(body, { expr: `context.StorageState(playwright.BrowserContextStorageStateOptions{Path: playwright.String(${doubleQuote(recording.header.saveStorage)})})`, returnsValue: true });
91
+ const w = new CodeWriter('\t');
92
+ w.line(`package ${goPackageName(options.packageName)}`);
93
+ w.line();
94
+ w.line('import (');
95
+ w.indent(() => {
96
+ for (const name of [...imports].sort())
97
+ w.line(doubleQuote(name));
98
+ w.line();
99
+ w.line(doubleQuote(playwrightGoModule));
100
+ w.line(doubleQuote('github.com/stretchr/testify/require'));
101
+ });
102
+ w.line(')');
103
+ w.line();
104
+ w.line('// Generated by testgen from a Playwright recording. Set HEADED=1 to watch the browser.');
105
+ w.block(`func Test${pascalCase(options.name)}(t *testing.T) {`, () => {
106
+ w.line('pw, err := playwright.Run()');
107
+ w.line('require.NoError(t, err)');
108
+ w.line('t.Cleanup(func() { _ = pw.Stop() })');
109
+ w.line();
110
+ const launchFields = [];
111
+ if (recording.header.launchOptions.channel)
112
+ launchFields.push(['Channel', `playwright.String(${doubleQuote(recording.header.launchOptions.channel)})`]);
113
+ launchFields.push(['Headless', 'playwright.Bool(os.Getenv("HEADED") == "")']);
114
+ w.line(`browser, err := pw.${browserType(recording.header)}.Launch(${structLiteral('playwright.BrowserTypeLaunchOptions', launchFields)})`);
115
+ w.line('require.NoError(t, err)');
116
+ w.line('t.Cleanup(func() { _ = browser.Close() })');
117
+ w.line();
118
+ const context = contextOptions(recording.header, options.testIdAttribute);
119
+ for (const line of context.before)
120
+ w.line(line);
121
+ w.line(`context, err := browser.NewContext(${context.fields.length ? structLiteral('playwright.BrowserNewContextOptions', context.fields) : ''})`);
122
+ w.line('require.NoError(t, err)');
123
+ if (options.testIdAttribute !== 'data-testid')
124
+ w.line(`pw.Selectors.SetTestIdAttribute(${doubleQuote(options.testIdAttribute)})`);
125
+ if (usesExpect)
126
+ w.line('expect := playwright.NewPlaywrightAssertions()');
127
+ w.line();
128
+ w.line(body.toString().trimEnd());
129
+ });
130
+ return [{ path: this.testFileName(options.name), content: w.toString() }];
131
+ },
132
+ };
133
+ function goPackageName(name) {
134
+ const cleaned = (name ?? '').toLowerCase().replace(/[^a-z0-9_]/g, '');
135
+ return cleaned && !/^[0-9]/.test(cleaned) ? cleaned : 'e2e';
136
+ }
137
+ function browserType(header) {
138
+ return { firefox: 'Firefox', webkit: 'WebKit' }[header.browserName] ?? 'Chromium';
139
+ }
140
+ function emit(w, call) {
141
+ if (call.returnsValue) {
142
+ w.line(`_, err = ${call.expr}`);
143
+ w.line('require.NoError(t, err)');
144
+ }
145
+ else {
146
+ w.line(`require.NoError(t, ${call.expr})`);
147
+ }
148
+ }
149
+ // func() error { ... } wrapping a call, for ExpectPopup / ExpectDownload.
150
+ function callback(call) {
151
+ const expr = call.expr.replace(/\n/g, '\n\t');
152
+ if (!call.returnsValue)
153
+ return `func() error {\n\treturn ${expr}\n}`;
154
+ return `func() error {\n\t_, err := ${expr}\n\treturn err\n}`;
155
+ }
156
+ // gofmt-compatible composite literal: one line for a single field, aligned values otherwise.
157
+ function structLiteral(type, fields) {
158
+ if (fields.length === 1)
159
+ return `${type}{${fields[0][0]}: ${fields[0][1]}}`;
160
+ const width = Math.max(...fields.map(([key]) => key.length));
161
+ const lines = fields.map(([key, value]) => `\t${key}:${' '.repeat(width - key.length + 1)}${value},`);
162
+ return `${type}{\n${lines.join('\n')}\n}`;
163
+ }
164
+ function contextOptions(header, testIdAttribute) {
165
+ const before = [];
166
+ const fields = [];
167
+ let options = { ...header.contextOptions };
168
+ const device = header.deviceName ? devices[header.deviceName] : undefined;
169
+ if (header.deviceName && device) {
170
+ before.push(`device := pw.Devices[${doubleQuote(header.deviceName)}]`);
171
+ fields.push(['UserAgent', 'playwright.String(device.UserAgent)'], ['Viewport', 'device.Viewport'], ['DeviceScaleFactor', 'playwright.Float(device.DeviceScaleFactor)'], ['IsMobile', 'playwright.Bool(device.IsMobile)'], ['HasTouch', 'playwright.Bool(device.HasTouch)']);
172
+ options = Object.fromEntries(Object.entries(options).filter(([key, value]) => JSON.stringify(device[key]) !== JSON.stringify(value)));
173
+ }
174
+ const unsupported = [];
175
+ const set = (key, value) => {
176
+ const existing = fields.findIndex(([name]) => name === key);
177
+ if (existing >= 0)
178
+ fields[existing] = [key, value];
179
+ else
180
+ fields.push([key, value]);
181
+ };
182
+ for (const [key, value] of Object.entries(options)) {
183
+ if (value === undefined || key === 'recordHar')
184
+ continue;
185
+ switch (key) {
186
+ case 'viewport':
187
+ if (value === null)
188
+ set('NoViewport', 'playwright.Bool(true)');
189
+ else
190
+ set('Viewport', `&playwright.Size{Width: ${value.width}, Height: ${value.height}}`);
191
+ break;
192
+ case 'userAgent':
193
+ set('UserAgent', `playwright.String(${doubleQuote(String(value))})`);
194
+ break;
195
+ case 'locale':
196
+ set('Locale', `playwright.String(${doubleQuote(String(value))})`);
197
+ break;
198
+ case 'timezoneId':
199
+ set('TimezoneId', `playwright.String(${doubleQuote(String(value))})`);
200
+ break;
201
+ case 'colorScheme':
202
+ set('ColorScheme', `playwright.ColorScheme${pascalCase(String(value))}`);
203
+ break;
204
+ case 'ignoreHTTPSErrors':
205
+ set('IgnoreHttpsErrors', `playwright.Bool(${!!value})`);
206
+ break;
207
+ case 'storageState':
208
+ set('StorageStatePath', `playwright.String(${doubleQuote(String(value))})`);
209
+ break;
210
+ case 'deviceScaleFactor':
211
+ set('DeviceScaleFactor', `playwright.Float(${Number(value)})`);
212
+ break;
213
+ case 'isMobile':
214
+ set('IsMobile', `playwright.Bool(${!!value})`);
215
+ break;
216
+ case 'hasTouch':
217
+ set('HasTouch', `playwright.Bool(${!!value})`);
218
+ break;
219
+ case 'geolocation':
220
+ set('Geolocation', `&playwright.Geolocation{Latitude: ${value.latitude}, Longitude: ${value.longitude}}`);
221
+ break;
222
+ case 'permissions':
223
+ set('Permissions', `[]string{${value.map(doubleQuote).join(', ')}}`);
224
+ break;
225
+ case 'screen': break;
226
+ default: unsupported.push(key);
227
+ }
228
+ }
229
+ if (unsupported.length)
230
+ before.unshift(`// TODO(testgen): context options not converted: ${unsupported.join(', ')}.`);
231
+ return { before, fields };
232
+ }
233
+ function actionCall(subject, action, locators) {
234
+ const locator = 'selector' in action ? locators.render(subject, parseLocator(action.selector)) : subject;
235
+ const expect = (assertion) => ({ expr: `expect.Locator(${locator}).${assertion}`, returnsValue: false });
236
+ switch (action.name) {
237
+ case 'click': {
238
+ const method = action.clickCount === 2 ? 'Dblclick' : 'Click';
239
+ const fields = [];
240
+ if (action.button !== 'left')
241
+ fields.push(['Button', `playwright.MouseButton${capitalize(action.button)}`]);
242
+ const modifiers = toKeyboardModifiers(action.modifiers);
243
+ if (modifiers.length)
244
+ fields.push(['Modifiers', `[]playwright.KeyboardModifier{${modifiers.map(m => `*playwright.KeyboardModifier${m}`).join(', ')}}`]);
245
+ if (action.clickCount > 2)
246
+ fields.push(['ClickCount', `playwright.Int(${action.clickCount})`]);
247
+ if (action.position)
248
+ fields.push(['Position', `&playwright.Position{X: ${action.position.x}, Y: ${action.position.y}}`]);
249
+ const options = fields.length ? structLiteral(`playwright.Locator${method}Options`, fields) : '';
250
+ return { expr: `${locator}.${method}(${options})`, returnsValue: false };
251
+ }
252
+ case 'hover': {
253
+ const options = action.position ? `playwright.LocatorHoverOptions{Position: &playwright.Position{X: ${action.position.x}, Y: ${action.position.y}}}` : '';
254
+ return { expr: `${locator}.Hover(${options})`, returnsValue: false };
255
+ }
256
+ case 'check':
257
+ return { expr: `${locator}.Check()`, returnsValue: false };
258
+ case 'uncheck':
259
+ return { expr: `${locator}.Uncheck()`, returnsValue: false };
260
+ case 'fill':
261
+ return { expr: `${locator}.Fill(${doubleQuote(action.text)})`, returnsValue: false };
262
+ case 'setInputFiles': {
263
+ const files = action.files.length === 1 ? doubleQuote(action.files[0]) : `[]string{${action.files.map(doubleQuote).join(', ')}}`;
264
+ return { expr: `${locator}.SetInputFiles(${files})`, returnsValue: false };
265
+ }
266
+ case 'press':
267
+ return { expr: `${locator}.Press(${doubleQuote(shortcutOf(action))})`, returnsValue: false };
268
+ case 'navigate':
269
+ return { expr: `${subject}.Goto(${doubleQuote(action.url)})`, returnsValue: true };
270
+ case 'select':
271
+ return { expr: `${locator}.SelectOption(playwright.SelectOptionValues{ValuesOrLabels: playwright.StringSlice(${action.options.map(doubleQuote).join(', ')})})`, returnsValue: true };
272
+ case 'assertText':
273
+ return expect(`${action.substring ? 'ToContainText' : 'ToHaveText'}(${doubleQuote(action.text)})`);
274
+ case 'assertChecked':
275
+ return expect(action.checked ? 'ToBeChecked()' : 'Not().ToBeChecked()');
276
+ case 'assertVisible':
277
+ return expect('ToBeVisible()');
278
+ case 'assertValue':
279
+ return expect(action.value ? `ToHaveValue(${doubleQuote(action.value)})` : 'ToBeEmpty()');
280
+ case 'assertSnapshot':
281
+ if (action.ariaSnapshot)
282
+ return expect(`ToMatchAriaSnapshot(${doubleQuote(action.ariaSnapshot)})`);
283
+ return { ...expect('ToBeVisible()'), todo: 'aria snapshot assertion; the recording does not include the snapshot.' };
284
+ case 'openPage':
285
+ case 'closePage':
286
+ throw new Error(`${action.name} is handled by the caller`);
287
+ }
288
+ }
289
+ // Renders Playwright locator steps as playwright-go calls.
290
+ class GoLocators {
291
+ _onRegexp;
292
+ constructor(_onRegexp) {
293
+ this._onRegexp = _onRegexp;
294
+ }
295
+ render(subject, steps) {
296
+ return subject + steps.map(step => '.' + this._step(subject, step)).join('');
297
+ }
298
+ _value(matcher) {
299
+ if (!isRegex(matcher))
300
+ return doubleQuote(matcher);
301
+ this._onRegexp();
302
+ const flags = matcher.flags.replace(/[^ims]/g, '');
303
+ const source = (flags ? `(?${flags})` : '') + matcher.source;
304
+ return `regexp.MustCompile(${source.includes('`') ? doubleQuote(source) : '`' + source + '`'})`;
305
+ }
306
+ _step(subject, step) {
307
+ const owner = { 'page': 'Page', 'locator': 'Locator', 'frame-locator': 'FrameLocator' }[step.base];
308
+ const body = step.body;
309
+ const filter = (field, value) => `Filter(playwright.LocatorFilterOptions{${field}: ${value}})`;
310
+ switch (step.kind) {
311
+ case 'default':
312
+ if (step.hasText !== undefined)
313
+ return `Locator(${doubleQuote(body)}, playwright.${owner}LocatorOptions{HasText: ${this._value(step.hasText)}})`;
314
+ if (step.hasNotText !== undefined)
315
+ return `Locator(${doubleQuote(body)}, playwright.${owner}LocatorOptions{HasNotText: ${this._value(step.hasNotText)}})`;
316
+ return `Locator(${doubleQuote(body)})`;
317
+ case 'frame-locator':
318
+ return `FrameLocator(${doubleQuote(body)})`;
319
+ case 'frame':
320
+ return 'ContentFrame()';
321
+ case 'any-frame':
322
+ return `Locator(${doubleQuote('internal:control=any-frame')})`;
323
+ case 'nth':
324
+ return `Nth(${body})`;
325
+ case 'first':
326
+ return 'First()';
327
+ case 'last':
328
+ return 'Last()';
329
+ case 'visible':
330
+ return filter('Visible', 'playwright.Bool(true)');
331
+ case 'filter-visible':
332
+ return filter('Visible', `playwright.Bool(${body === 'true'})`);
333
+ case 'has-text':
334
+ return filter('HasText', this._value(step.body));
335
+ case 'has-not-text':
336
+ return filter('HasNotText', this._value(step.body));
337
+ case 'has':
338
+ return filter('Has', this.render(subject, step.inner));
339
+ case 'hasNot':
340
+ return filter('HasNot', this.render(subject, step.inner));
341
+ case 'and':
342
+ return `And(${this.render(subject, step.inner)})`;
343
+ case 'or':
344
+ return `Or(${this.render(subject, step.inner)})`;
345
+ case 'chain':
346
+ return `Locator(${this.render(subject, step.inner)})`;
347
+ case 'test-id':
348
+ return `GetByTestId(${this._value(step.body)})`;
349
+ case 'role':
350
+ return this._role(owner, step);
351
+ case 'text':
352
+ return this._byText(owner, 'GetByText', step);
353
+ case 'alt':
354
+ return this._byText(owner, 'GetByAltText', step);
355
+ case 'placeholder':
356
+ return this._byText(owner, 'GetByPlaceholder', step);
357
+ case 'label':
358
+ return this._byText(owner, 'GetByLabel', step);
359
+ case 'title':
360
+ return this._byText(owner, 'GetByTitle', step);
361
+ }
362
+ }
363
+ _byText(owner, method, step) {
364
+ if (isRegex(step.body) || !step.exact)
365
+ return `${method}(${this._value(step.body)})`;
366
+ return `${method}(${this._value(step.body)}, playwright.${owner}${method}Options{Exact: playwright.Bool(true)})`;
367
+ }
368
+ _role(owner, step) {
369
+ const role = step.body;
370
+ const roleValue = ariaRoles.has(role) ? `*playwright.AriaRole${capitalize(role)}` : `playwright.AriaRole(${doubleQuote(role)})`;
371
+ const fields = [];
372
+ if (step.name !== undefined)
373
+ fields.push(`Name: ${this._value(step.name)}`);
374
+ if (step.description !== undefined)
375
+ fields.push(`Description: ${this._value(step.description)}`);
376
+ if (step.exact && (typeof step.name === 'string' || typeof step.description === 'string'))
377
+ fields.push('Exact: playwright.Bool(true)');
378
+ for (const { name, value } of step.attrs ?? []) {
379
+ const field = capitalize(name);
380
+ if (typeof value === 'boolean')
381
+ fields.push(`${field}: playwright.Bool(${value})`);
382
+ else if (typeof value === 'number')
383
+ fields.push(`${field}: playwright.Int(${value})`);
384
+ }
385
+ return fields.length ? `GetByRole(${roleValue}, playwright.${owner}GetByRoleOptions{${fields.join(', ')}})` : `GetByRole(${roleValue})`;
386
+ }
387
+ }
388
+ //# sourceMappingURL=playwright-go.js.map
@@ -0,0 +1,50 @@
1
+ import { devices } from 'playwright-core';
2
+ import { formatObject } from '../vendor/playwright/stringUtils.js';
3
+ import { kebabCase, words } from '../naming.js';
4
+ import { writeJavaScriptBody } from './javascript-body.js';
5
+ import { CodeWriter, singleQuote } from './writer.js';
6
+ export const playwrightTs = {
7
+ id: 'playwright-ts',
8
+ framework: 'Playwright Test',
9
+ language: 'TypeScript',
10
+ highlight: 'typescript',
11
+ testFileName: name => `${kebabCase(name)}.spec.ts`,
12
+ generate(recording, options) {
13
+ const { header } = recording;
14
+ const w = new CodeWriter(' ');
15
+ w.line(`import { test, expect${header.deviceName ? ', devices' : ''} } from '@playwright/test';`);
16
+ const use = useOptions(header, options.testIdAttribute);
17
+ if (use) {
18
+ w.line();
19
+ w.line(`test.use(${use});`);
20
+ }
21
+ w.line();
22
+ w.block(`test(${singleQuote(words(options.name).join(' '))}, async ({ page }) => {`, () => {
23
+ writeJavaScriptBody(w, recording, {
24
+ supportsDownloads: true,
25
+ todo: message => `// TODO(testgen): ${message}`,
26
+ });
27
+ }, '});');
28
+ return [{ path: this.testFileName(options.name), content: w.toString() }];
29
+ },
30
+ };
31
+ // Context options for test.use(), spreading the device descriptor like Playwright's codegen.
32
+ function useOptions(header, testIdAttribute) {
33
+ const options = { ...header.contextOptions, recordHar: undefined };
34
+ if (testIdAttribute !== 'data-testid')
35
+ options.testIdAttribute = testIdAttribute;
36
+ const device = header.deviceName ? devices[header.deviceName] : undefined;
37
+ if (!device) {
38
+ const text = formatObject(options);
39
+ return text === '{}' ? '' : text;
40
+ }
41
+ // Only keep what differs from the device descriptor.
42
+ for (const key of Object.keys(options)) {
43
+ if (JSON.stringify(device[key]) === JSON.stringify(options[key]))
44
+ delete options[key];
45
+ }
46
+ const lines = formatObject(options).split('\n');
47
+ const spread = ` ...devices[${singleQuote(header.deviceName)}],`;
48
+ return lines.length === 1 ? `{\n${spread}\n}` : [lines[0], spread, ...lines.slice(1)].join('\n');
49
+ }
50
+ //# sourceMappingURL=playwright-ts.js.map
@@ -0,0 +1,16 @@
1
+ import fs from 'node:fs';
2
+ // Support files ship as real sources under runtime/ (next to src/ and dist/).
3
+ const runtimeRoot = new URL('../../runtime/', import.meta.url);
4
+ export function readRuntimeFile(relativePath) {
5
+ return fs.readFileSync(new URL(relativePath, runtimeRoot), 'utf8');
6
+ }
7
+ // In-page locator engine shared by every Selenium support file.
8
+ export function engineSource() {
9
+ return readRuntimeFile('selenium/engine.js');
10
+ }
11
+ // Reads a support file and splices the engine in place of its `@testgen-engine` marker.
12
+ export function readRuntime(relativePath, embedEngine = source => source) {
13
+ const engine = embedEngine(engineSource().trimEnd());
14
+ return readRuntimeFile(relativePath).replace(/\/\* @testgen-engine \*\/|@testgen-engine/, () => engine);
15
+ }
16
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1,38 @@
1
+ // Selenium browser name for the browser the recording was made with.
2
+ export function seleniumBrowser(header) {
3
+ if (header.browserName === 'firefox')
4
+ return 'firefox';
5
+ if (header.browserName === 'webkit')
6
+ return 'safari';
7
+ return header.launchOptions.channel?.startsWith('msedge') ? 'edge' : 'chrome';
8
+ }
9
+ const ignoredContextOptions = new Set(['recordHar', 'recordVideo', 'deviceScaleFactor', 'isMobile', 'hasTouch', 'screen']);
10
+ export function seleniumDriverOptions(header) {
11
+ const browser = seleniumBrowser(header);
12
+ const result = { args: [], unsupported: [] };
13
+ for (const [key, value] of Object.entries(header.contextOptions)) {
14
+ if (value === undefined || value === null || ignoredContextOptions.has(key))
15
+ continue;
16
+ if (key === 'viewport') {
17
+ result.viewport = value;
18
+ }
19
+ else if (key === 'locale' && (browser === 'chrome' || browser === 'edge')) {
20
+ result.args.push(`--lang=${value}`);
21
+ }
22
+ else if (key === 'userAgent' && (browser === 'chrome' || browser === 'edge')) {
23
+ result.args.push(`--user-agent=${value}`);
24
+ }
25
+ else if (key === 'ignoreHTTPSErrors' && value === true && (browser === 'chrome' || browser === 'edge')) {
26
+ result.args.push('--ignore-certificate-errors');
27
+ }
28
+ else {
29
+ result.unsupported.push(key);
30
+ }
31
+ }
32
+ if (header.deviceName)
33
+ result.unsupported.push(`device "${header.deviceName}" (only its viewport and user agent are applied)`);
34
+ if (header.saveStorage)
35
+ result.unsupported.push(`saving the storage state to ${header.saveStorage}`);
36
+ return result;
37
+ }
38
+ //# sourceMappingURL=selenium-common.js.map