qa360 2.0.13 → 2.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,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
+ }
@@ -1,6 +1,13 @@
1
1
  /**
2
2
  * QA360 Playwright UI Adapter (Extended)
3
3
  * Complete UI E2E testing with all Playwright actions
4
+ *
5
+ * Playwright++ Features:
6
+ * - Video recording (always/retain-on-fail/never)
7
+ * - Automatic screenshots (before/after steps, on error)
8
+ * - Trace capture for debugging
9
+ * - Artifacts management with CAS
10
+ * - HTML report generation
4
11
  */
5
12
  import type { WebTarget, PackBudgets, UiTestDefinition, UiTestStep } from '../types/pack-v1.js';
6
13
  import { AuthCredentials } from '../auth/index.js';
@@ -19,6 +26,17 @@ export interface UiTestConfig {
19
26
  };
20
27
  /** CLI override for headed mode */
21
28
  cliHeaded?: boolean;
29
+ /** Playwright++: Artifacts configuration */
30
+ artifacts?: {
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
+ outputDir?: string;
35
+ };
36
+ /** Playwright++: HTML report generation */
37
+ htmlReport?: string;
38
+ /** Playwright++: Bail after N failures */
39
+ bail?: number;
22
40
  }
23
41
  export interface UiTestResult {
24
42
  page: string;
@@ -27,6 +45,12 @@ export interface UiTestResult {
27
45
  error?: string;
28
46
  screenshot?: string;
29
47
  video?: string;
48
+ /** Playwright++: Artifacts paths */
49
+ artifacts?: {
50
+ screenshots: string[];
51
+ videos: string[];
52
+ traces: string[];
53
+ };
30
54
  accessibility?: {
31
55
  score: number;
32
56
  violations: Array<{
@@ -84,6 +108,12 @@ export declare class PlaywrightUiAdapter {
84
108
  private artifactDir;
85
109
  private videoDir;
86
110
  private traceDir;
111
+ private artifactsManager?;
112
+ private failureCount;
113
+ private currentTestId?;
114
+ private allScreenshots;
115
+ private allVideos;
116
+ private allTraces;
87
117
  constructor();
88
118
  /**
89
119
  * Set authentication credentials for requests
@@ -91,14 +121,17 @@ export declare class PlaywrightUiAdapter {
91
121
  setAuth(credentials?: AuthCredentials): void;
92
122
  /**
93
123
  * Execute UI smoke tests with accessibility
124
+ * Playwright++: Supports artifacts, screenshots, video, trace, HTML reporting
94
125
  */
95
126
  runSmokeTests(config: UiTestConfig): Promise<UiSmokeResult>;
96
127
  /**
97
128
  * Run a single E2E test
129
+ * Playwright++: Takes before/after screenshots, captures artifacts on failure
98
130
  */
99
131
  runE2eTest(test: UiTestDefinition, config: UiTestConfig): Promise<UiE2eResult>;
100
132
  /**
101
133
  * Execute a single UI test step
134
+ * Playwright++: Enhanced error handling with artifacts
102
135
  */
103
136
  private executeStep;
104
137
  /**
@@ -111,12 +144,17 @@ export declare class PlaywrightUiAdapter {
111
144
  private testPage;
112
145
  /**
113
146
  * Setup browser with all options
147
+ * Playwright++: Enhanced video/trace recording support
114
148
  */
115
149
  private setupBrowser;
116
150
  /**
117
151
  * Determine if video should be recorded
118
152
  */
119
153
  private shouldRecordVideo;
154
+ /**
155
+ * Playwright++: Generate HTML report
156
+ */
157
+ private generateHtmlReport;
120
158
  /**
121
159
  * Perform login if configured
122
160
  */