qa360 2.0.13 → 2.1.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.
Files changed (56) hide show
  1. package/dist/commands/scan.d.ts +5 -0
  2. package/dist/commands/scan.js +155 -0
  3. package/dist/core/adapters/playwright-native-adapter.d.ts +121 -0
  4. package/dist/core/adapters/playwright-native-adapter.js +339 -0
  5. package/dist/core/adapters/playwright-ui.d.ts +38 -0
  6. package/dist/core/adapters/playwright-ui.js +164 -4
  7. package/dist/core/artifacts/index.d.ts +6 -0
  8. package/dist/core/artifacts/index.js +6 -0
  9. package/dist/core/artifacts/ui-artifacts.d.ts +133 -0
  10. package/dist/core/artifacts/ui-artifacts.js +304 -0
  11. package/dist/core/core/coverage/analyzer.d.ts +101 -0
  12. package/dist/core/core/coverage/analyzer.js +415 -0
  13. package/dist/core/core/coverage/collector.d.ts +74 -0
  14. package/dist/core/core/coverage/collector.js +459 -0
  15. package/dist/core/core/coverage/config.d.ts +37 -0
  16. package/dist/core/core/coverage/config.js +156 -0
  17. package/dist/core/core/coverage/index.d.ts +11 -0
  18. package/dist/core/core/coverage/index.js +15 -0
  19. package/dist/core/core/coverage/types.d.ts +267 -0
  20. package/dist/core/core/coverage/types.js +6 -0
  21. package/dist/core/core/coverage/vault.d.ts +95 -0
  22. package/dist/core/core/coverage/vault.js +405 -0
  23. package/dist/core/index.d.ts +6 -0
  24. package/dist/core/index.js +9 -0
  25. package/dist/core/parallel/index.d.ts +6 -0
  26. package/dist/core/parallel/index.js +6 -0
  27. package/dist/core/parallel/parallel-runner.d.ts +107 -0
  28. package/dist/core/parallel/parallel-runner.js +192 -0
  29. package/dist/core/reporting/html-reporter.d.ts +119 -0
  30. package/dist/core/reporting/html-reporter.js +737 -0
  31. package/dist/core/reporting/index.d.ts +6 -0
  32. package/dist/core/reporting/index.js +6 -0
  33. package/dist/core/runner/phase3-runner.js +29 -4
  34. package/dist/core/vault/cas.d.ts +5 -1
  35. package/dist/core/vault/cas.js +6 -0
  36. package/dist/core/visual/index.d.ts +6 -0
  37. package/dist/core/visual/index.js +6 -0
  38. package/dist/core/visual/visual-regression.d.ts +113 -0
  39. package/dist/core/visual/visual-regression.js +236 -0
  40. package/dist/generators/index.d.ts +5 -0
  41. package/dist/generators/index.js +5 -0
  42. package/dist/generators/json-reporter.d.ts +10 -0
  43. package/dist/generators/json-reporter.js +12 -0
  44. package/dist/generators/test-generator.d.ts +18 -0
  45. package/dist/generators/test-generator.js +78 -0
  46. package/dist/index.js +3 -0
  47. package/dist/scanners/dom-scanner.d.ts +52 -0
  48. package/dist/scanners/dom-scanner.js +296 -0
  49. package/dist/scanners/index.d.ts +4 -0
  50. package/dist/scanners/index.js +4 -0
  51. package/dist/types/scan.d.ts +68 -0
  52. package/dist/types/scan.js +4 -0
  53. package/examples/README.md +38 -0
  54. package/examples/crawler.yml +38 -0
  55. package/examples/ui-advanced.yml +49 -0
  56. package/package.json +2 -2
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Scan Command - Discover UI elements from a web page
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare const scanCommand: Command;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Scan Command - Discover UI elements from a web page
3
+ */
4
+ import { Command } from 'commander';
5
+ import { chromium } from '@playwright/test';
6
+ import { DOMScanner } from '../scanners/dom-scanner.js';
7
+ import { JSONReporter } from '../generators/json-reporter.js';
8
+ import { TestGenerator } from '../generators/test-generator.js';
9
+ import { resolve } from 'path';
10
+ import { mkdirSync } from 'fs';
11
+ export const scanCommand = new Command('scan');
12
+ scanCommand
13
+ .description('Scan a web page for UI elements and generate test configurations')
14
+ .argument('<url>', 'URL of the page to scan')
15
+ .option('-o, --output <file>', 'Output file path (default: ".qa360/discovery/elements.json")')
16
+ .option('-i, --include <types>', 'Element types to include: buttons,links,forms,inputs,images,headings,all (default: "all")')
17
+ .option('-e, --exclude <selectors>', 'CSS selectors to exclude (comma-separated)')
18
+ .option('-s, --screenshot', 'Capture screenshots of elements (not yet implemented)')
19
+ .option('-a, --auto-generate-test', 'Auto-generate QA360 test file')
20
+ .option('-t, --timeout <ms>', 'Navigation timeout in milliseconds', '30000')
21
+ .option('--headed', 'Run in headed mode (show browser)')
22
+ .action(async (url, options) => {
23
+ const startTime = Date.now();
24
+ // Validate URL
25
+ try {
26
+ new URL(url);
27
+ }
28
+ catch {
29
+ console.error(`❌ Invalid URL: ${url}`);
30
+ process.exit(1);
31
+ }
32
+ // Parse include types
33
+ const includeTypes = options.include || 'all';
34
+ const include = includeTypes
35
+ .split(',')
36
+ .map((t) => t.trim().toLowerCase());
37
+ // Validate element types
38
+ const validTypes = ['buttons', 'links', 'forms', 'inputs', 'images', 'headings', 'all'];
39
+ for (const type of include) {
40
+ if (!validTypes.includes(type)) {
41
+ console.error(`❌ Invalid element type: ${type}`);
42
+ console.error(` Valid types: ${validTypes.join(', ')}`);
43
+ process.exit(1);
44
+ }
45
+ }
46
+ // Parse exclude selectors
47
+ const exclude = options.exclude
48
+ ? options.exclude.split(',').map((s) => s.trim())
49
+ : [];
50
+ const scanOptions = {
51
+ url,
52
+ output: options.output || '.qa360/discovery/elements.json',
53
+ include,
54
+ exclude,
55
+ screenshot: options.screenshot || false,
56
+ autoGenerateTest: options.autoGenerateTest || false,
57
+ timeout: parseInt(options.timeout, 10) || 30000,
58
+ headless: !options.headed
59
+ };
60
+ console.log(`🔍 Scanning page: ${url}`);
61
+ console.log('━'.repeat(50));
62
+ let browser = null;
63
+ let context = null;
64
+ let page = null;
65
+ try {
66
+ // Launch browser
67
+ browser = await chromium.launch({
68
+ headless: scanOptions.headless
69
+ });
70
+ context = await browser.newContext({
71
+ viewport: { width: 1280, height: 720 }
72
+ });
73
+ page = await context.newPage();
74
+ // Navigate to URL
75
+ await page.goto(url, {
76
+ timeout: scanOptions.timeout,
77
+ waitUntil: 'networkidle'
78
+ });
79
+ // Run scan
80
+ const scanner = new DOMScanner(page, url);
81
+ const elements = await scanner.scan(scanOptions);
82
+ // Generate summary
83
+ const summary = {
84
+ totalElements: elements.length,
85
+ buttons: elements.filter(e => e.type === 'button').length,
86
+ links: elements.filter(e => e.type === 'link').length,
87
+ forms: elements.filter(e => e.type === 'form').length,
88
+ inputs: elements.filter(e => e.type === 'input').length,
89
+ images: elements.filter(e => e.type === 'image').length,
90
+ headings: elements.filter(e => e.type === 'heading').length
91
+ };
92
+ // Print summary
93
+ console.log(`✅ Buttons found: ${summary.buttons}`);
94
+ console.log(`✅ Links found: ${summary.links}`);
95
+ console.log(`✅ Forms found: ${summary.forms}`);
96
+ console.log(`✅ Inputs found: ${summary.inputs}`);
97
+ console.log(`✅ Images found: ${summary.images}`);
98
+ console.log(`✅ Headings found: ${summary.headings}`);
99
+ console.log('━'.repeat(50));
100
+ console.log(`📦 Total elements: ${summary.totalElements}`);
101
+ console.log();
102
+ // Create output directory
103
+ const outputPath = resolve(scanOptions.output);
104
+ const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
105
+ mkdirSync(outputDir, { recursive: true });
106
+ // Generate scan result
107
+ const result = {
108
+ scanDate: new Date().toISOString(),
109
+ url,
110
+ duration: Date.now() - startTime,
111
+ summary,
112
+ elements
113
+ };
114
+ // Generate JSON report
115
+ const reporter = new JSONReporter();
116
+ await reporter.generate(result, outputPath);
117
+ console.log(`💾 Saved to: ${outputPath}`);
118
+ // Auto-generate test if requested
119
+ if (options.autoGenerateTest) {
120
+ const testGenerator = new TestGenerator();
121
+ const testPath = outputPath.replace('.json', '.yml');
122
+ await testGenerator.generate(result, testPath);
123
+ console.log(`📄 Test generated: ${testPath}`);
124
+ }
125
+ // Screenshots (placeholder for future implementation)
126
+ if (options.screenshot) {
127
+ console.log(`📸 Screenshots: ${outputDir}/screenshots/ (not yet implemented)`);
128
+ }
129
+ // Cleanup
130
+ await page.close();
131
+ await context.close();
132
+ await browser.close();
133
+ console.log();
134
+ console.log(`✨ Scan completed in ${((Date.now() - startTime) / 1000).toFixed(1)}s`);
135
+ console.log();
136
+ console.log('💡 Next steps:');
137
+ console.log(` • Review the discovered elements`);
138
+ if (options.autoGenerateTest) {
139
+ console.log(` • Run the auto-generated test: qa360 run ${outputPath.replace('.json', '.yml')}`);
140
+ }
141
+ console.log(` • Edit the test to remove unwanted elements`);
142
+ }
143
+ catch (error) {
144
+ // Cleanup on error
145
+ if (page)
146
+ await page.close().catch(() => { });
147
+ if (context)
148
+ await context.close().catch(() => { });
149
+ if (browser)
150
+ await browser.close().catch(() => { });
151
+ const errorMessage = error instanceof Error ? error.message : String(error);
152
+ console.error(`❌ Scan failed: ${errorMessage}`);
153
+ process.exit(1);
154
+ }
155
+ });
@@ -0,0 +1,121 @@
1
+ /**
2
+ * QA360 Playwright Native Adapter
3
+ *
4
+ * Zero-overhead adapter that uses Playwright's native API directly.
5
+ * This bypasses any wrapper logic and gives full access to Playwright's capabilities.
6
+ *
7
+ * Benefits:
8
+ * - Zero performance overhead
9
+ * - Full Playwright API access
10
+ * - Native video/trace/screenshot support
11
+ * - Direct access to browser context, page, locators
12
+ */
13
+ import { Browser, BrowserContext, Page, Locator } from '@playwright/test';
14
+ import type { WebTarget, PackBudgets, UiTestDefinition, UiTestStep } from '../types/pack-v1.js';
15
+ export interface PlaywrightNativeConfig {
16
+ target: WebTarget;
17
+ budgets?: PackBudgets;
18
+ timeout?: number;
19
+ browser?: 'chromium' | 'firefox' | 'webkit';
20
+ headless?: boolean;
21
+ slowMo?: number;
22
+ devtools?: boolean;
23
+ device?: 'desktop' | 'tablet' | 'mobile' | 'custom';
24
+ viewport?: {
25
+ width: number;
26
+ height: number;
27
+ };
28
+ userAgent?: string;
29
+ locale?: string;
30
+ timezone?: string;
31
+ screenshots?: 'always' | 'only-on-failure' | 'never';
32
+ video?: 'always' | 'retain-on-failure' | 'never';
33
+ trace?: 'always' | 'retain-on-failure' | 'never' | 'on-first-failure';
34
+ bail?: number;
35
+ workers?: number;
36
+ ignoreHTTPSErrors?: boolean;
37
+ acceptDownloads?: boolean;
38
+ bypassCSP?: boolean;
39
+ }
40
+ export interface PlaywrightNativeResult {
41
+ success: boolean;
42
+ duration: number;
43
+ artifacts?: {
44
+ screenshots: string[];
45
+ videos: string[];
46
+ traces: string[];
47
+ };
48
+ error?: string;
49
+ coverage?: {
50
+ lines: number;
51
+ statements: number;
52
+ branches: number;
53
+ functions: number;
54
+ };
55
+ }
56
+ export interface PlaywrightNativeStepResult {
57
+ step: UiTestStep;
58
+ success: boolean;
59
+ duration: number;
60
+ error?: string;
61
+ screenshot?: string;
62
+ }
63
+ /**
64
+ * Playwright Native Adapter
65
+ *
66
+ * Provides zero-overhead access to Playwright with automatic
67
+ * artifact collection (screenshots, videos, traces).
68
+ */
69
+ export declare class PlaywrightNativeAdapter {
70
+ private config;
71
+ private browser?;
72
+ private context?;
73
+ private page?;
74
+ private artifacts?;
75
+ private assertions?;
76
+ private testResults;
77
+ private failureCount;
78
+ constructor(config: PlaywrightNativeConfig);
79
+ /**
80
+ * Run a single E2E test with full Playwright native access
81
+ */
82
+ runTest(test: UiTestDefinition): Promise<PlaywrightNativeResult>;
83
+ /**
84
+ * Execute a single step using Playwright native API
85
+ */
86
+ private executeStep;
87
+ /**
88
+ * Execute action using Playwright native API
89
+ */
90
+ private executeAction;
91
+ /**
92
+ * Setup browser with trace context
93
+ */
94
+ private setup;
95
+ /**
96
+ * Teardown and save artifacts
97
+ */
98
+ private teardown;
99
+ /**
100
+ * Get artifact paths for result
101
+ */
102
+ private getArtifactPaths;
103
+ /**
104
+ * Get direct access to Playwright objects
105
+ */
106
+ getBrowser(): Browser | undefined;
107
+ getContext(): BrowserContext | undefined;
108
+ getPage(): Page | undefined;
109
+ /**
110
+ * Get locator for direct manipulation
111
+ */
112
+ locator(selector: string): Locator;
113
+ }
114
+ /**
115
+ * Create a Playwright Native Adapter
116
+ */
117
+ export declare function createPlaywrightNativeAdapter(config: PlaywrightNativeConfig): PlaywrightNativeAdapter;
118
+ /**
119
+ * Create adapter from WebTarget (convenience function)
120
+ */
121
+ export declare function createFromTarget(target: WebTarget, options?: Partial<PlaywrightNativeConfig>): PlaywrightNativeAdapter;
@@ -0,0 +1,339 @@
1
+ /**
2
+ * QA360 Playwright Native Adapter
3
+ *
4
+ * Zero-overhead adapter that uses Playwright's native API directly.
5
+ * This bypasses any wrapper logic and gives full access to Playwright's capabilities.
6
+ *
7
+ * Benefits:
8
+ * - Zero performance overhead
9
+ * - Full Playwright API access
10
+ * - Native video/trace/screenshot support
11
+ * - Direct access to browser context, page, locators
12
+ */
13
+ import { chromium, firefox, webkit } from '@playwright/test';
14
+ import { UIArtifactsManager } from '../artifacts/ui-artifacts.js';
15
+ import { createAssertionsEngine } from '../assertions/index.js';
16
+ /**
17
+ * Playwright Native Adapter
18
+ *
19
+ * Provides zero-overhead access to Playwright with automatic
20
+ * artifact collection (screenshots, videos, traces).
21
+ */
22
+ export class PlaywrightNativeAdapter {
23
+ config;
24
+ browser;
25
+ context;
26
+ page;
27
+ artifacts;
28
+ assertions;
29
+ // Test state tracking
30
+ testResults = new Map();
31
+ failureCount = 0;
32
+ constructor(config) {
33
+ this.config = config;
34
+ // Initialize artifacts manager
35
+ this.artifacts = new UIArtifactsManager('.qa360/artifacts/playwright-native', '.qa360/runs/cas');
36
+ }
37
+ /**
38
+ * Run a single E2E test with full Playwright native access
39
+ */
40
+ async runTest(test) {
41
+ const startTime = Date.now();
42
+ const testId = test.name || 'unknown';
43
+ this.artifacts?.startTest(testId);
44
+ this.testResults.set(testId, []);
45
+ try {
46
+ // Setup browser with trace context if enabled
47
+ await this.setup();
48
+ // Navigate to start URL
49
+ const startUrl = test.url || `${this.config.target.baseUrl.replace(/\/$/, '')}${test.path || ''}`;
50
+ await this.page.goto(startUrl, { timeout: this.config.timeout || 30000 });
51
+ // Initialize assertions engine
52
+ this.assertions = createAssertionsEngine(this.page);
53
+ // Take initial screenshot
54
+ if (this.config.screenshots !== 'never') {
55
+ await this.artifacts?.takeScreenshot(this.page, {}, {
56
+ testId,
57
+ type: 'screenshot',
58
+ tags: ['initial'],
59
+ });
60
+ }
61
+ // Execute each step
62
+ for (let i = 0; i < test.steps.length; i++) {
63
+ const step = test.steps[i];
64
+ const stepResult = await this.executeStep(step, i);
65
+ this.testResults.get(testId).push(stepResult);
66
+ if (!stepResult.success) {
67
+ this.failureCount++;
68
+ // Check if we should bail
69
+ if (this.config.bail && this.failureCount >= this.config.bail) {
70
+ throw new Error(`Bail: ${this.failureCount} failures`);
71
+ }
72
+ // Check if we should continue on failure
73
+ const continueOnFailure = step.continueOnError ?? false;
74
+ if (!continueOnFailure) {
75
+ break;
76
+ }
77
+ }
78
+ // Screenshot after each step if configured
79
+ if (this.config.screenshots === 'always' || (this.config.screenshots === 'only-on-failure' && !stepResult.success)) {
80
+ await this.artifacts?.takeAfterScreenshot(this.page, step.action || 'step', i, stepResult.success);
81
+ }
82
+ }
83
+ // All steps passed
84
+ const duration = Date.now() - startTime;
85
+ return {
86
+ success: true,
87
+ duration,
88
+ artifacts: this.getArtifactPaths(),
89
+ };
90
+ }
91
+ catch (error) {
92
+ // Take error screenshot
93
+ if (this.page) {
94
+ await this.artifacts?.takeErrorScreenshot(this.page, error);
95
+ }
96
+ return {
97
+ success: false,
98
+ duration: Date.now() - startTime,
99
+ error: error instanceof Error ? error.message : String(error),
100
+ artifacts: this.getArtifactPaths(),
101
+ };
102
+ }
103
+ finally {
104
+ this.artifacts?.endTest();
105
+ await this.teardown();
106
+ }
107
+ }
108
+ /**
109
+ * Execute a single step using Playwright native API
110
+ */
111
+ async executeStep(step, index) {
112
+ const startTime = Date.now();
113
+ try {
114
+ // Take before screenshot if enabled
115
+ if (this.config.screenshots === 'always') {
116
+ await this.artifacts?.takeBeforeScreenshot(this.page, step.action || 'step', index);
117
+ }
118
+ // Execute action based on type
119
+ const result = await this.executeAction(step);
120
+ // Execute assertions if present
121
+ if (step.assertions && step.assertions.length > 0 && this.assertions) {
122
+ for (const assertion of step.assertions) {
123
+ const assertionResult = await this.assertions.runAssertion(assertion);
124
+ if (!assertionResult.passed && !assertion.soft) {
125
+ throw new Error(assertionResult.error || `Assertion failed: ${assertion.type}`);
126
+ }
127
+ }
128
+ }
129
+ return {
130
+ step,
131
+ success: true,
132
+ duration: Date.now() - startTime,
133
+ };
134
+ }
135
+ catch (error) {
136
+ return {
137
+ step,
138
+ success: false,
139
+ duration: Date.now() - startTime,
140
+ error: error instanceof Error ? error.message : String(error),
141
+ };
142
+ }
143
+ }
144
+ /**
145
+ * Execute action using Playwright native API
146
+ */
147
+ async executeAction(step) {
148
+ const selector = step.selector;
149
+ const page = this.page;
150
+ const timeout = step.timeout || this.config.timeout || 30000;
151
+ // Ensure selector is defined for actions that need it
152
+ const needsSelector = ['click', 'dblClick', 'rightClick', 'hover', 'focus', 'fill', 'type',
153
+ 'clear', 'select', 'check', 'uncheck', 'press', 'upload', 'tap', 'scroll'].includes(step.action);
154
+ if (needsSelector && !selector) {
155
+ throw new Error(`Selector is required for action: ${step.action}`);
156
+ }
157
+ switch (step.action) {
158
+ case 'navigate':
159
+ await page.goto(step.value || '', { timeout });
160
+ break;
161
+ case 'click':
162
+ await page.click(selector, { timeout });
163
+ break;
164
+ case 'dblClick':
165
+ await page.dblclick(selector, { timeout });
166
+ break;
167
+ case 'rightClick':
168
+ await page.click(selector, { button: 'right', timeout });
169
+ break;
170
+ case 'fill':
171
+ case 'type':
172
+ if (step.value) {
173
+ await page.fill(selector, step.value, { timeout });
174
+ }
175
+ break;
176
+ case 'clear':
177
+ await page.fill(selector, '', { timeout });
178
+ break;
179
+ case 'select':
180
+ if (step.value !== undefined) {
181
+ await page.selectOption(selector, step.value, { timeout });
182
+ }
183
+ break;
184
+ case 'check':
185
+ await page.check(selector, { timeout });
186
+ break;
187
+ case 'uncheck':
188
+ await page.uncheck(selector, { timeout });
189
+ break;
190
+ case 'hover':
191
+ await page.hover(selector, { timeout });
192
+ break;
193
+ case 'focus':
194
+ await page.focus(selector, { timeout });
195
+ break;
196
+ case 'press':
197
+ if (step.value) {
198
+ await page.press(selector, step.value, { timeout });
199
+ }
200
+ break;
201
+ case 'upload':
202
+ if (step.value) {
203
+ await page.setInputFiles(selector, step.value, { timeout });
204
+ }
205
+ break;
206
+ case 'dragAndDrop':
207
+ if (step.value) {
208
+ const source = page.locator(selector);
209
+ const target = page.locator(step.value);
210
+ await source.dragTo(target, { timeout });
211
+ }
212
+ break;
213
+ case 'scroll':
214
+ if (selector) {
215
+ await page.locator(selector).scrollIntoViewIfNeeded({ timeout });
216
+ }
217
+ break;
218
+ case 'waitFor':
219
+ case 'waitForSelector':
220
+ if (selector) {
221
+ await page.waitForSelector(selector, {
222
+ state: step.value || undefined,
223
+ timeout
224
+ });
225
+ }
226
+ break;
227
+ case 'waitForTimeout':
228
+ await page.waitForTimeout(parseInt(step.value || '0', 10) || 0);
229
+ break;
230
+ case 'waitForNavigation':
231
+ await page.waitForNavigation({ timeout });
232
+ break;
233
+ case 'tap':
234
+ await page.tap(selector, { timeout });
235
+ break;
236
+ default:
237
+ throw new Error(`Unknown action: ${step.action}`);
238
+ }
239
+ }
240
+ /**
241
+ * Setup browser with trace context
242
+ */
243
+ async setup() {
244
+ const browserType = this.config.browser || 'chromium';
245
+ const browserTypeObj = browserType === 'firefox' ? firefox : browserType === 'webkit' ? webkit : chromium;
246
+ // Launch browser
247
+ this.browser = await browserTypeObj.launch({
248
+ headless: this.config.headless ?? true,
249
+ slowMo: this.config.slowMo || 0,
250
+ devtools: this.config.devtools || false,
251
+ });
252
+ // Create context with trace recording if enabled
253
+ const contextOptions = {
254
+ viewport: this.config.viewport || { width: 1280, height: 720 },
255
+ userAgent: this.config.userAgent,
256
+ locale: this.config.locale || 'en-US',
257
+ timezoneId: this.config.timezone,
258
+ acceptDownloads: this.config.acceptDownloads ?? true,
259
+ ignoreHTTPSErrors: this.config.ignoreHTTPSErrors,
260
+ bypassCSP: this.config.bypassCSP,
261
+ };
262
+ // Add video recording
263
+ if (this.config.video === 'always' || this.config.video === 'retain-on-failure') {
264
+ contextOptions.recordVideo = {
265
+ dir: '.qa360/artifacts/playwright-native/videos',
266
+ size: this.config.viewport || { width: 1280, height: 720 },
267
+ };
268
+ }
269
+ this.context = await this.browser.newContext(contextOptions);
270
+ // Add tracing if enabled
271
+ if (this.config.trace === 'always' || this.config.trace === 'on-first-failure' || this.config.trace === 'retain-on-failure') {
272
+ // Start tracing (Playwright's trace feature)
273
+ // Note: tracing needs to be started per test
274
+ }
275
+ this.page = await this.context.newPage();
276
+ // Set default timeout
277
+ this.page.setDefaultTimeout(this.config.timeout || 30000);
278
+ }
279
+ /**
280
+ * Teardown and save artifacts
281
+ */
282
+ async teardown() {
283
+ if (this.page) {
284
+ // Stop tracing if enabled
285
+ // Save trace file
286
+ await this.page.close();
287
+ }
288
+ if (this.context) {
289
+ // Save video if enabled and tests failed
290
+ if (this.config.video === 'retain-on-failure' && this.failureCount > 0) {
291
+ // Videos are automatically saved by Playwright
292
+ }
293
+ await this.context.close();
294
+ }
295
+ if (this.browser) {
296
+ await this.browser.close();
297
+ }
298
+ }
299
+ /**
300
+ * Get artifact paths for result
301
+ */
302
+ getArtifactPaths() {
303
+ return {
304
+ screenshots: [],
305
+ videos: [],
306
+ traces: [],
307
+ };
308
+ }
309
+ /**
310
+ * Get direct access to Playwright objects
311
+ */
312
+ getBrowser() { return this.browser; }
313
+ getContext() { return this.context; }
314
+ getPage() { return this.page; }
315
+ /**
316
+ * Get locator for direct manipulation
317
+ */
318
+ locator(selector) {
319
+ if (!this.page) {
320
+ throw new Error('Page not initialized. Call setup() first.');
321
+ }
322
+ return this.page.locator(selector);
323
+ }
324
+ }
325
+ /**
326
+ * Create a Playwright Native Adapter
327
+ */
328
+ export function createPlaywrightNativeAdapter(config) {
329
+ return new PlaywrightNativeAdapter(config);
330
+ }
331
+ /**
332
+ * Create adapter from WebTarget (convenience function)
333
+ */
334
+ export function createFromTarget(target, options = {}) {
335
+ return new PlaywrightNativeAdapter({
336
+ target,
337
+ ...options,
338
+ });
339
+ }