@samsara-dev/appwright 0.2.0 → 0.3.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.
- package/CHANGELOG.md +20 -0
- package/dist/device/index.d.ts +11 -1
- package/dist/device/index.d.ts.map +1 -1
- package/dist/device/index.js +21 -1
- package/dist/fixture/index.d.ts.map +1 -1
- package/dist/fixture/index.js +5 -0
- package/dist/locator/index.d.ts +4 -1
- package/dist/locator/index.d.ts.map +1 -1
- package/dist/locator/index.js +4 -1
- package/dist/tests/visual-trace.service.spec.d.ts +2 -0
- package/dist/tests/visual-trace.service.spec.d.ts.map +1 -0
- package/dist/tests/visual-trace.service.spec.js +298 -0
- package/dist/types/index.d.ts +26 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/utils.d.ts +16 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +81 -1
- package/dist/visualTrace/index.d.ts +3 -0
- package/dist/visualTrace/index.d.ts.map +1 -0
- package/dist/visualTrace/index.js +9 -0
- package/dist/visualTrace/service.d.ts +75 -0
- package/dist/visualTrace/service.d.ts.map +1 -0
- package/dist/visualTrace/service.js +249 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# appwright
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9415229: feat: add Visual Trace Service for automatic screenshot capture during test execution
|
|
8
|
+
|
|
9
|
+
- Implements automatic screenshot capture with smart defaults (only captures for failed tests)
|
|
10
|
+
- Adds screenshot deduplication using SHA-256 hashing
|
|
11
|
+
- Supports configurable screenshot limits (default: 50)
|
|
12
|
+
- Integrates with Playwright's trace configuration modes
|
|
13
|
+
- Works with both test-scoped device and worker-scoped persistentDevice fixtures
|
|
14
|
+
- Provides proper test isolation and retry support
|
|
15
|
+
- Includes comprehensive unit test coverage
|
|
16
|
+
|
|
17
|
+
## 0.2.1
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 493ddf8: Add horizontal scroll directions (left/right) to ScrollDirection enum and update documentation with examples
|
|
22
|
+
|
|
3
23
|
## 0.2.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/dist/device/index.d.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import type { Client as WebDriverClient } from "webdriver";
|
|
2
|
-
import { AppwrightLocator, ExtractType, Platform, TimeoutOptions } from "../types";
|
|
2
|
+
import { AppwrightLocator, ExtractType, Platform, TimeoutOptions, VisualTraceConfig } from "../types";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { LLMModel } from "@empiricalrun/llm";
|
|
5
|
+
import { TestInfo } from "@playwright/test";
|
|
5
6
|
export declare class Device {
|
|
6
7
|
private webDriverClient;
|
|
7
8
|
private bundleId;
|
|
8
9
|
private timeoutOpts;
|
|
9
10
|
private provider;
|
|
11
|
+
private visualTraceService?;
|
|
10
12
|
constructor(webDriverClient: WebDriverClient, bundleId: string | undefined, timeoutOpts: TimeoutOptions, provider: string);
|
|
13
|
+
/**
|
|
14
|
+
* Initialize Visual Trace Service for screenshot capture during test execution
|
|
15
|
+
*/
|
|
16
|
+
initializeVisualTrace(testInfo: TestInfo, retryIndex: number, config?: VisualTraceConfig): void;
|
|
17
|
+
/**
|
|
18
|
+
* Take a screenshot - exposed for Visual Trace Service
|
|
19
|
+
*/
|
|
20
|
+
takeScreenshot(): Promise<Buffer>;
|
|
11
21
|
locator({ selector, findStrategy, textToMatch, }: {
|
|
12
22
|
selector: string;
|
|
13
23
|
findStrategy: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/device/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,WAAW,CAAC;AAE3D,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,QAAQ,EACR,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/device/index.ts"],"names":[],"mappings":"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"}
|
package/dist/device/index.js
CHANGED
|
@@ -42,6 +42,7 @@ const utils_1 = require("../utils");
|
|
|
42
42
|
const utils_2 = require("../providers/browserstack/utils");
|
|
43
43
|
const utils_3 = require("../providers/lambdatest/utils");
|
|
44
44
|
const logger_1 = require("../logger");
|
|
45
|
+
const visualTrace_1 = require("../visualTrace");
|
|
45
46
|
let Device = (() => {
|
|
46
47
|
let _instanceExtraInitializers = [];
|
|
47
48
|
let _tap_decorators;
|
|
@@ -83,14 +84,28 @@ let Device = (() => {
|
|
|
83
84
|
bundleId;
|
|
84
85
|
timeoutOpts;
|
|
85
86
|
provider;
|
|
87
|
+
visualTraceService;
|
|
86
88
|
constructor(webDriverClient, bundleId, timeoutOpts, provider) {
|
|
87
89
|
this.webDriverClient = webDriverClient;
|
|
88
90
|
this.bundleId = bundleId;
|
|
89
91
|
this.timeoutOpts = timeoutOpts;
|
|
90
92
|
this.provider = provider;
|
|
91
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Initialize Visual Trace Service for screenshot capture during test execution
|
|
96
|
+
*/
|
|
97
|
+
initializeVisualTrace(testInfo, retryIndex, config) {
|
|
98
|
+
this.visualTraceService = (0, visualTrace_1.initializeVisualTrace)(testInfo, retryIndex, config);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Take a screenshot - exposed for Visual Trace Service
|
|
102
|
+
*/
|
|
103
|
+
async takeScreenshot() {
|
|
104
|
+
const base64Screenshot = await this.webDriverClient.takeScreenshot();
|
|
105
|
+
return Buffer.from(base64Screenshot, "base64");
|
|
106
|
+
}
|
|
92
107
|
locator({ selector, findStrategy, textToMatch, }) {
|
|
93
|
-
return new locator_1.Locator(this.webDriverClient, this.timeoutOpts, selector, findStrategy, textToMatch);
|
|
108
|
+
return new locator_1.Locator(this.webDriverClient, this.timeoutOpts, selector, findStrategy, textToMatch, this);
|
|
94
109
|
}
|
|
95
110
|
vision() {
|
|
96
111
|
return new vision_1.VisionProvider(this, this.webDriverClient);
|
|
@@ -121,6 +136,11 @@ let Device = (() => {
|
|
|
121
136
|
catch (e) {
|
|
122
137
|
logger_1.logger.error(`close:`, e);
|
|
123
138
|
}
|
|
139
|
+
// Clean up visual trace service
|
|
140
|
+
if (this.visualTraceService) {
|
|
141
|
+
(0, visualTrace_1.clearVisualTraceService)();
|
|
142
|
+
this.visualTraceService = undefined;
|
|
143
|
+
}
|
|
124
144
|
}
|
|
125
145
|
/**
|
|
126
146
|
* Tap on the screen at the given coordinates, specified as x and y. The top left corner
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixture/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixture/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,EAGd,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAKnC,KAAK,iBAAiB,GAAG;IACvB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAE/B;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,IAAI,uRAyEf,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM;2BACY,gBAAgB,YAAY,aAAa;;;;;;;EAUtE,CAAC"}
|
package/dist/fixture/index.js
CHANGED
|
@@ -21,6 +21,9 @@ exports.test = test_1.test.extend({
|
|
|
21
21
|
type: "sessionId",
|
|
22
22
|
description: deviceProvider.sessionId,
|
|
23
23
|
});
|
|
24
|
+
// Initialize Visual Trace Service for screenshot capture
|
|
25
|
+
const visualTraceConfig = testInfo.project.use.visualTrace;
|
|
26
|
+
device.initializeVisualTrace(testInfo, testInfo.retry, visualTraceConfig);
|
|
24
27
|
await deviceProvider.syncTestDetails?.({ name: testInfo.title });
|
|
25
28
|
await use(device);
|
|
26
29
|
await device.close();
|
|
@@ -46,6 +49,8 @@ exports.test = test_1.test.extend({
|
|
|
46
49
|
}
|
|
47
50
|
const providerName = project.use.device
|
|
48
51
|
?.provider;
|
|
52
|
+
// Note: For persistentDevice, Visual Trace is initialized lazily in boxedStep
|
|
53
|
+
// when test.info() is available, ensuring it works for worker-scoped fixtures.
|
|
49
54
|
const afterSession = new Date();
|
|
50
55
|
const workerInfoStore = new workerInfo_1.WorkerInfoStore();
|
|
51
56
|
await workerInfoStore.saveWorkerStartTime(workerIndex, sessionId, providerName, beforeSession, afterSession);
|
package/dist/locator/index.d.ts
CHANGED
|
@@ -6,7 +6,10 @@ export declare class Locator {
|
|
|
6
6
|
private selector;
|
|
7
7
|
private findStrategy;
|
|
8
8
|
private textToMatch?;
|
|
9
|
-
|
|
9
|
+
private device?;
|
|
10
|
+
constructor(webDriverClient: WebDriverClient, timeoutOpts: TimeoutOptions, selector: string, findStrategy: string, textToMatch?: (string | RegExp) | undefined, device?: {
|
|
11
|
+
takeScreenshot: () => Promise<Buffer>;
|
|
12
|
+
});
|
|
10
13
|
fill(value: string, options?: ActionOptions): Promise<void>;
|
|
11
14
|
sendKeyStrokes(value: string, options?: ActionOptions): Promise<void>;
|
|
12
15
|
isVisible(options?: ActionOptions): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/locator/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,WAAW,CAAC;AAEtD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,aAAa,EAEd,MAAM,UAAU,CAAC;AAIlB,qBAAa,OAAO;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/locator/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,WAAW,CAAC;AAEtD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,aAAa,EAEd,MAAM,UAAU,CAAC;AAIlB,qBAAa,OAAO;IAKhB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,WAAW;IAEnB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,YAAY;IAEpB,OAAO,CAAC,WAAW,CAAC;IATtB,OAAO,CAAC,MAAM,CAAC,CAA4C;gBAGjD,eAAe,EAAE,eAAe,EAChC,WAAW,EAAE,cAAc,EAE3B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EAEpB,WAAW,CAAC,GAAE,MAAM,GAAG,MAAM,aAAA,EACrC,MAAM,CAAC,EAAE;QAAE,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;KAAE;IAM9C,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB3D,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCrE,SAAS,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAYpD,OAAO,CACX,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC;YAgCF,SAAS;IAiCjB,GAAG,CAAC,OAAO,CAAC,EAAE,aAAa;IAiB3B,OAAO,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBjD,MAAM,CAAC,SAAS,EAAE,eAAe;IAuBvC;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;CAkDrD"}
|
package/dist/locator/index.js
CHANGED
|
@@ -69,16 +69,19 @@ let Locator = (() => {
|
|
|
69
69
|
selector;
|
|
70
70
|
findStrategy;
|
|
71
71
|
textToMatch;
|
|
72
|
+
// Store reference to the device for Visual Trace Service
|
|
73
|
+
device;
|
|
72
74
|
constructor(webDriverClient, timeoutOpts,
|
|
73
75
|
// Used for find elements request that is sent to Appium server
|
|
74
76
|
selector, findStrategy,
|
|
75
77
|
// Used to filter elements received from Appium server
|
|
76
|
-
textToMatch) {
|
|
78
|
+
textToMatch, device) {
|
|
77
79
|
this.webDriverClient = webDriverClient;
|
|
78
80
|
this.timeoutOpts = timeoutOpts;
|
|
79
81
|
this.selector = selector;
|
|
80
82
|
this.findStrategy = findStrategy;
|
|
81
83
|
this.textToMatch = textToMatch;
|
|
84
|
+
this.device = device;
|
|
82
85
|
}
|
|
83
86
|
async fill(value, options) {
|
|
84
87
|
const isElementDisplayed = await this.isVisible(options);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-trace.service.spec.d.ts","sourceRoot":"","sources":["../../src/tests/visual-trace.service.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const service_1 = require("../visualTrace/service");
|
|
5
|
+
// Mock TestInfo object
|
|
6
|
+
function createMockTestInfo(options) {
|
|
7
|
+
return {
|
|
8
|
+
testId: options.testId || "test-1",
|
|
9
|
+
retry: options.retry || 0,
|
|
10
|
+
status: options.status,
|
|
11
|
+
errors: options.errors || [],
|
|
12
|
+
project: {
|
|
13
|
+
use: {
|
|
14
|
+
trace: options.trace,
|
|
15
|
+
screenshots: options.screenshots,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
attach: vitest_1.vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Mock screenshot function
|
|
22
|
+
const mockTakeScreenshot = vitest_1.vi.fn(() => Promise.resolve(Buffer.from("mock-screenshot-data")));
|
|
23
|
+
(0, vitest_1.describe)("VisualTraceService", () => {
|
|
24
|
+
(0, vitest_1.beforeEach)(() => {
|
|
25
|
+
(0, service_1.clearVisualTraceService)();
|
|
26
|
+
mockTakeScreenshot.mockClear();
|
|
27
|
+
});
|
|
28
|
+
(0, vitest_1.describe)("Configuration", () => {
|
|
29
|
+
(0, vitest_1.test)("should use default configuration when no config provided", () => {
|
|
30
|
+
const testInfo = createMockTestInfo({ testId: "test-1" });
|
|
31
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
32
|
+
(0, vitest_1.expect)(service["config"]).toEqual({
|
|
33
|
+
enableScreenshots: "retain-on-failure",
|
|
34
|
+
maxScreenshots: 50,
|
|
35
|
+
dedupe: true,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.test)("should merge provided config with defaults", () => {
|
|
39
|
+
const testInfo = createMockTestInfo({ testId: "test-1" });
|
|
40
|
+
const config = {
|
|
41
|
+
enableScreenshots: "on",
|
|
42
|
+
maxScreenshots: 10,
|
|
43
|
+
};
|
|
44
|
+
const service = new service_1.VisualTraceService(testInfo, 0, config);
|
|
45
|
+
(0, vitest_1.expect)(service["config"]).toEqual({
|
|
46
|
+
enableScreenshots: "on",
|
|
47
|
+
maxScreenshots: 10,
|
|
48
|
+
dedupe: true, // Default value
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.describe)("shouldCaptureScreenshot", () => {
|
|
53
|
+
(0, vitest_1.test)('should return true when trace mode is "on"', () => {
|
|
54
|
+
const testInfo = createMockTestInfo({ trace: "on" });
|
|
55
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
56
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot()).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
(0, vitest_1.test)('should return false when trace mode is "off"', () => {
|
|
59
|
+
const testInfo = createMockTestInfo({ trace: "off" });
|
|
60
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
61
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot()).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
(0, vitest_1.test)("should return false when screenshots are explicitly disabled", () => {
|
|
64
|
+
const testInfo = createMockTestInfo({ trace: "on" });
|
|
65
|
+
const service = new service_1.VisualTraceService(testInfo, 0, {
|
|
66
|
+
enableScreenshots: false,
|
|
67
|
+
});
|
|
68
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot()).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
(0, vitest_1.test)('should return true for failed test with "retain-on-failure"', () => {
|
|
71
|
+
const testInfo = createMockTestInfo({
|
|
72
|
+
trace: "retain-on-failure",
|
|
73
|
+
status: "failed",
|
|
74
|
+
});
|
|
75
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
76
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot()).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
(0, vitest_1.test)('should return false for passed test with "retain-on-failure"', () => {
|
|
79
|
+
const testInfo = createMockTestInfo({
|
|
80
|
+
trace: "retain-on-failure",
|
|
81
|
+
status: "passed",
|
|
82
|
+
});
|
|
83
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
84
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot()).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
(0, vitest_1.test)('should return true when step fails with "retain-on-failure"', () => {
|
|
87
|
+
const testInfo = createMockTestInfo({
|
|
88
|
+
trace: "retain-on-failure",
|
|
89
|
+
status: undefined, // Status not set during execution
|
|
90
|
+
});
|
|
91
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
92
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot(true)).toBe(true); // stepFailed = true
|
|
93
|
+
});
|
|
94
|
+
(0, vitest_1.test)('should handle object trace configuration with mode "on"', () => {
|
|
95
|
+
const testInfo = {
|
|
96
|
+
testId: "test-1",
|
|
97
|
+
retry: 0,
|
|
98
|
+
status: undefined,
|
|
99
|
+
errors: [],
|
|
100
|
+
project: {
|
|
101
|
+
use: {
|
|
102
|
+
trace: { mode: "on", screenshots: true },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
attach: vitest_1.vi.fn(),
|
|
106
|
+
};
|
|
107
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
108
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot()).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
(0, vitest_1.test)("should respect screenshots: false in object trace config", () => {
|
|
111
|
+
const testInfo = {
|
|
112
|
+
testId: "test-1",
|
|
113
|
+
retry: 0,
|
|
114
|
+
status: undefined,
|
|
115
|
+
errors: [],
|
|
116
|
+
project: {
|
|
117
|
+
use: {
|
|
118
|
+
trace: { mode: "on", screenshots: false },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
attach: vitest_1.vi.fn(),
|
|
122
|
+
};
|
|
123
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
124
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot()).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
(0, vitest_1.test)('should return true on retry with "on-first-retry"', () => {
|
|
127
|
+
const testInfo = createMockTestInfo({ trace: "on-first-retry" });
|
|
128
|
+
const service = new service_1.VisualTraceService(testInfo, 1);
|
|
129
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot()).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
(0, vitest_1.test)('should return false on initial run with "on-first-retry"', () => {
|
|
132
|
+
const testInfo = createMockTestInfo({ trace: "on-first-retry" });
|
|
133
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
134
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot()).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
(0, vitest_1.test)('should return true on all retries with "on-all-retries"', () => {
|
|
137
|
+
const testInfo = createMockTestInfo({ trace: "on-all-retries" });
|
|
138
|
+
const service1 = new service_1.VisualTraceService(testInfo, 1);
|
|
139
|
+
const service2 = new service_1.VisualTraceService(testInfo, 2);
|
|
140
|
+
(0, vitest_1.expect)(service1.shouldCaptureScreenshot()).toBe(true);
|
|
141
|
+
(0, vitest_1.expect)(service2.shouldCaptureScreenshot()).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
(0, vitest_1.test)("should return true when test has errors", () => {
|
|
144
|
+
const testInfo = createMockTestInfo({
|
|
145
|
+
trace: "retain-on-failure",
|
|
146
|
+
errors: [new Error("Test failed")],
|
|
147
|
+
});
|
|
148
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
149
|
+
(0, vitest_1.expect)(service.shouldCaptureScreenshot()).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
(0, vitest_1.describe)("captureScreenshot", () => {
|
|
153
|
+
(0, vitest_1.test)("should capture screenshot when conditions are met", async () => {
|
|
154
|
+
const testInfo = createMockTestInfo({ trace: "on" });
|
|
155
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
156
|
+
await service.captureScreenshot(mockTakeScreenshot, "test-step");
|
|
157
|
+
(0, vitest_1.expect)(mockTakeScreenshot).toHaveBeenCalled();
|
|
158
|
+
(0, vitest_1.expect)(testInfo.attach).toHaveBeenCalledWith(vitest_1.expect.stringContaining("screenshot-"), vitest_1.expect.objectContaining({
|
|
159
|
+
body: vitest_1.expect.any(Buffer),
|
|
160
|
+
contentType: "image/png",
|
|
161
|
+
}));
|
|
162
|
+
});
|
|
163
|
+
(0, vitest_1.test)("should not capture screenshot when disabled", async () => {
|
|
164
|
+
const testInfo = createMockTestInfo({ trace: "off" });
|
|
165
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
166
|
+
await service.captureScreenshot(mockTakeScreenshot, "test-step");
|
|
167
|
+
(0, vitest_1.expect)(mockTakeScreenshot).not.toHaveBeenCalled();
|
|
168
|
+
(0, vitest_1.expect)(testInfo.attach).not.toHaveBeenCalled();
|
|
169
|
+
});
|
|
170
|
+
(0, vitest_1.test)("should include step title in filename", async () => {
|
|
171
|
+
const testInfo = createMockTestInfo({ trace: "on" });
|
|
172
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
173
|
+
await service.captureScreenshot(mockTakeScreenshot, "click-submit");
|
|
174
|
+
(0, vitest_1.expect)(testInfo.attach).toHaveBeenCalledWith(vitest_1.expect.stringContaining("click_submit"), vitest_1.expect.objectContaining({
|
|
175
|
+
body: vitest_1.expect.any(Buffer),
|
|
176
|
+
contentType: "image/png",
|
|
177
|
+
}));
|
|
178
|
+
});
|
|
179
|
+
(0, vitest_1.test)("should handle screenshot capture errors gracefully", async () => {
|
|
180
|
+
const testInfo = createMockTestInfo({ trace: "on" });
|
|
181
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
182
|
+
const errorTakeScreenshot = vitest_1.vi.fn(() => Promise.reject(new Error("Screenshot failed")));
|
|
183
|
+
// Mock console.warn to verify it's called
|
|
184
|
+
const consoleWarnSpy = vitest_1.vi
|
|
185
|
+
.spyOn(console, "warn")
|
|
186
|
+
.mockImplementation(() => { });
|
|
187
|
+
await service.captureScreenshot(errorTakeScreenshot, "test-step");
|
|
188
|
+
(0, vitest_1.expect)(consoleWarnSpy).toHaveBeenCalledWith(vitest_1.expect.stringContaining("Failed to capture screenshot"));
|
|
189
|
+
(0, vitest_1.expect)(testInfo.attach).not.toHaveBeenCalled();
|
|
190
|
+
consoleWarnSpy.mockRestore();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
(0, vitest_1.describe)("Screenshot limits", () => {
|
|
194
|
+
(0, vitest_1.test)("should respect max screenshot limit", async () => {
|
|
195
|
+
const testInfo = createMockTestInfo({ trace: "on" });
|
|
196
|
+
const service = new service_1.VisualTraceService(testInfo, 0, {
|
|
197
|
+
maxScreenshots: 2,
|
|
198
|
+
dedupe: false,
|
|
199
|
+
});
|
|
200
|
+
// Capture first two screenshots
|
|
201
|
+
await service.captureScreenshot(mockTakeScreenshot, "step-1");
|
|
202
|
+
await service.captureScreenshot(mockTakeScreenshot, "step-2");
|
|
203
|
+
// Third screenshot should be skipped
|
|
204
|
+
await service.captureScreenshot(mockTakeScreenshot, "step-3");
|
|
205
|
+
(0, vitest_1.expect)(mockTakeScreenshot).toHaveBeenCalledTimes(2);
|
|
206
|
+
(0, vitest_1.expect)(service.getScreenshotCount()).toBe(2);
|
|
207
|
+
});
|
|
208
|
+
(0, vitest_1.test)("should track screenshot count correctly", async () => {
|
|
209
|
+
const testInfo = createMockTestInfo({ trace: "on" });
|
|
210
|
+
const service = new service_1.VisualTraceService(testInfo, 0, { dedupe: false });
|
|
211
|
+
(0, vitest_1.expect)(service.getScreenshotCount()).toBe(0);
|
|
212
|
+
await service.captureScreenshot(mockTakeScreenshot, "step-1");
|
|
213
|
+
(0, vitest_1.expect)(service.getScreenshotCount()).toBe(1);
|
|
214
|
+
await service.captureScreenshot(mockTakeScreenshot, "step-2");
|
|
215
|
+
(0, vitest_1.expect)(service.getScreenshotCount()).toBe(2);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
(0, vitest_1.describe)("Deduplication", () => {
|
|
219
|
+
(0, vitest_1.test)("should skip duplicate screenshots when dedupe is enabled", async () => {
|
|
220
|
+
const testInfo = createMockTestInfo({ trace: "on" });
|
|
221
|
+
const service = new service_1.VisualTraceService(testInfo, 0, { dedupe: true });
|
|
222
|
+
// Same screenshot data
|
|
223
|
+
const sameData = Buffer.from("same-screenshot");
|
|
224
|
+
const sameTakeScreenshot = vitest_1.vi.fn(() => Promise.resolve(sameData));
|
|
225
|
+
await service.captureScreenshot(sameTakeScreenshot, "step-1");
|
|
226
|
+
await service.captureScreenshot(sameTakeScreenshot, "step-2");
|
|
227
|
+
(0, vitest_1.expect)(sameTakeScreenshot).toHaveBeenCalledTimes(2);
|
|
228
|
+
(0, vitest_1.expect)(testInfo.attach).toHaveBeenCalledTimes(1); // Only attached once
|
|
229
|
+
});
|
|
230
|
+
(0, vitest_1.test)("should capture all screenshots when dedupe is disabled", async () => {
|
|
231
|
+
const testInfo = createMockTestInfo({ trace: "on" });
|
|
232
|
+
const service = new service_1.VisualTraceService(testInfo, 0, { dedupe: false });
|
|
233
|
+
// Same screenshot data
|
|
234
|
+
const sameData = Buffer.from("same-screenshot");
|
|
235
|
+
const sameTakeScreenshot = vitest_1.vi.fn(() => Promise.resolve(sameData));
|
|
236
|
+
await service.captureScreenshot(sameTakeScreenshot, "step-1");
|
|
237
|
+
await service.captureScreenshot(sameTakeScreenshot, "step-2");
|
|
238
|
+
(0, vitest_1.expect)(sameTakeScreenshot).toHaveBeenCalledTimes(2);
|
|
239
|
+
(0, vitest_1.expect)(testInfo.attach).toHaveBeenCalledTimes(2); // Attached both times
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
(0, vitest_1.describe)("State management", () => {
|
|
243
|
+
(0, vitest_1.test)("should maintain separate state for different test attempts", async () => {
|
|
244
|
+
const testInfo = createMockTestInfo({ testId: "test-1", trace: "on" });
|
|
245
|
+
// First attempt
|
|
246
|
+
const service1 = new service_1.VisualTraceService(testInfo, 0);
|
|
247
|
+
await service1.captureScreenshot(mockTakeScreenshot, "step-1");
|
|
248
|
+
(0, vitest_1.expect)(service1.getScreenshotCount()).toBe(1);
|
|
249
|
+
// Second attempt (retry)
|
|
250
|
+
const service2 = new service_1.VisualTraceService(testInfo, 1);
|
|
251
|
+
(0, vitest_1.expect)(service2.getScreenshotCount()).toBe(0); // Should start fresh
|
|
252
|
+
await service2.captureScreenshot(mockTakeScreenshot, "step-1");
|
|
253
|
+
(0, vitest_1.expect)(service2.getScreenshotCount()).toBe(1);
|
|
254
|
+
// First attempt state should be preserved
|
|
255
|
+
(0, vitest_1.expect)(service1.getScreenshotCount()).toBe(1);
|
|
256
|
+
});
|
|
257
|
+
(0, vitest_1.test)("should reset state when resetState is called", () => {
|
|
258
|
+
const testInfo = createMockTestInfo({ testId: "test-1", trace: "on" });
|
|
259
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
260
|
+
service["getState"]().screenshotCount = 5;
|
|
261
|
+
(0, vitest_1.expect)(service.getScreenshotCount()).toBe(5);
|
|
262
|
+
service.resetState();
|
|
263
|
+
(0, vitest_1.expect)(service.getScreenshotCount()).toBe(0);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
(0, vitest_1.describe)("Singleton management", () => {
|
|
267
|
+
(0, vitest_1.test)("should initialize and get service instance", () => {
|
|
268
|
+
const testInfo = createMockTestInfo({ testId: "test-1" });
|
|
269
|
+
const service = (0, service_1.initializeVisualTrace)(testInfo, 0);
|
|
270
|
+
(0, vitest_1.expect)(service).toBeInstanceOf(service_1.VisualTraceService);
|
|
271
|
+
const retrievedService = (0, service_1.getVisualTraceService)();
|
|
272
|
+
(0, vitest_1.expect)(retrievedService).toBe(service);
|
|
273
|
+
});
|
|
274
|
+
(0, vitest_1.test)("should clear service instance", () => {
|
|
275
|
+
const testInfo = createMockTestInfo({ testId: "test-1" });
|
|
276
|
+
(0, service_1.initializeVisualTrace)(testInfo, 0);
|
|
277
|
+
(0, vitest_1.expect)((0, service_1.getVisualTraceService)()).not.toBeNull();
|
|
278
|
+
(0, service_1.clearVisualTraceService)();
|
|
279
|
+
(0, vitest_1.expect)((0, service_1.getVisualTraceService)()).toBeNull();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
(0, vitest_1.describe)("Configuration update", () => {
|
|
283
|
+
(0, vitest_1.test)("should update configuration at runtime", () => {
|
|
284
|
+
const testInfo = createMockTestInfo({ testId: "test-1" });
|
|
285
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
286
|
+
(0, vitest_1.expect)(service["config"].enableScreenshots).toBe("retain-on-failure");
|
|
287
|
+
service.updateConfig({ enableScreenshots: "on" });
|
|
288
|
+
(0, vitest_1.expect)(service["config"].enableScreenshots).toBe("on");
|
|
289
|
+
});
|
|
290
|
+
(0, vitest_1.test)("should merge partial config updates", () => {
|
|
291
|
+
const testInfo = createMockTestInfo({ testId: "test-1" });
|
|
292
|
+
const service = new service_1.VisualTraceService(testInfo, 0);
|
|
293
|
+
service.updateConfig({ maxScreenshots: 100 });
|
|
294
|
+
(0, vitest_1.expect)(service["config"].maxScreenshots).toBe(100);
|
|
295
|
+
(0, vitest_1.expect)(service["config"].dedupe).toBe(true); // Should preserve other settings
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|
package/dist/types/index.d.ts
CHANGED
|
@@ -43,6 +43,29 @@ export type AppwrightConfig = {
|
|
|
43
43
|
appBundleId: string;
|
|
44
44
|
expectTimeout: number;
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Configuration for Visual Trace screenshot capture
|
|
48
|
+
*/
|
|
49
|
+
export type VisualTraceConfig = {
|
|
50
|
+
/**
|
|
51
|
+
* Controls when screenshots are captured:
|
|
52
|
+
* - true | 'on': Always capture screenshots
|
|
53
|
+
* - false | 'off': Never capture screenshots
|
|
54
|
+
* - 'retain-on-failure': Only capture screenshots when tests fail (default)
|
|
55
|
+
*/
|
|
56
|
+
enableScreenshots?: boolean | "on" | "off" | "retain-on-failure";
|
|
57
|
+
/**
|
|
58
|
+
* Maximum number of screenshots to capture per test.
|
|
59
|
+
* Default: 50
|
|
60
|
+
*/
|
|
61
|
+
maxScreenshots?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Whether to deduplicate screenshots based on content hash.
|
|
64
|
+
* Prevents capturing identical screenshots multiple times.
|
|
65
|
+
* Default: true
|
|
66
|
+
*/
|
|
67
|
+
dedupe?: boolean;
|
|
68
|
+
};
|
|
46
69
|
export type DeviceConfig = BrowserStackConfig | LambdaTestConfig | LocalDeviceConfig | EmulatorConfig;
|
|
47
70
|
/**
|
|
48
71
|
* Configuration for devices running on Browserstack.
|
|
@@ -150,7 +173,9 @@ export declare enum DeviceOrientation {
|
|
|
150
173
|
}
|
|
151
174
|
export declare enum ScrollDirection {
|
|
152
175
|
UP = "up",
|
|
153
|
-
DOWN = "down"
|
|
176
|
+
DOWN = "down",
|
|
177
|
+
LEFT = "left",
|
|
178
|
+
RIGHT = "right"
|
|
154
179
|
}
|
|
155
180
|
export interface AppwrightLocator {
|
|
156
181
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.js
CHANGED
|
@@ -15,6 +15,8 @@ var ScrollDirection;
|
|
|
15
15
|
(function (ScrollDirection) {
|
|
16
16
|
ScrollDirection["UP"] = "up";
|
|
17
17
|
ScrollDirection["DOWN"] = "down";
|
|
18
|
+
ScrollDirection["LEFT"] = "left";
|
|
19
|
+
ScrollDirection["RIGHT"] = "right";
|
|
18
20
|
})(ScrollDirection || (exports.ScrollDirection = ScrollDirection = {}));
|
|
19
21
|
var WebDriverErrors;
|
|
20
22
|
(function (WebDriverErrors) {
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
|
+
import { TestInfo } from "@playwright/test";
|
|
1
2
|
export declare function boxedStep(target: Function, context: ClassMethodDecoratorContext): (this: {
|
|
2
|
-
selector
|
|
3
|
+
selector?: string | RegExp;
|
|
4
|
+
device?: {
|
|
5
|
+
takeScreenshot: () => Promise<Buffer>;
|
|
6
|
+
initializeVisualTrace?: (testInfo: any, retryIndex: number, config?: any) => void;
|
|
7
|
+
};
|
|
8
|
+
takeScreenshot?: () => Promise<Buffer>;
|
|
9
|
+
initializeVisualTrace?: (testInfo: any, retryIndex: number, config?: any) => void;
|
|
3
10
|
}, ...args: any) => Promise<any>;
|
|
4
11
|
export declare function validateBuildPath(buildPath: string | undefined, expectedExtension: string): void;
|
|
5
12
|
export declare function getLatestBuildToolsVersions(versions: string[]): string | undefined;
|
|
6
13
|
export declare function longestDeterministicGroup(pattern: RegExp): string | undefined;
|
|
7
14
|
export declare function basePath(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Check if tracing is enabled for the current test based on Playwright's trace configuration
|
|
17
|
+
*/
|
|
18
|
+
export declare function isTracingEnabled(testInfo: TestInfo, retryIndex: number): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Determine if screenshots should be captured based on trace mode
|
|
21
|
+
*/
|
|
22
|
+
export declare function shouldCaptureScreenshotsFromTrace(testInfo: TestInfo, retryIndex: number): boolean;
|
|
8
23
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAQlD,wBAAgB,SAAS,CACvB,MAAM,EAAE,QAAQ,EAChB,OAAO,EAAE,2BAA2B,UAG5B;IACJ,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE;QACP,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QACtC,qBAAqB,CAAC,EAAE,CACtB,QAAQ,EAAE,GAAG,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,GAAG,KACT,IAAI,CAAC;KACX,CAAC;IACF,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,qBAAqB,CAAC,EAAE,CACtB,QAAQ,EAAE,GAAG,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,GAAG,KACT,IAAI,CAAC;CACX,WACQ,GAAG,kBAwEf;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,iBAAiB,EAAE,MAAM,QAoB1B;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAAE,GACjB,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAuB7E;AAED,wBAAgB,QAAQ,WAEvB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CA2BT;AAED;;GAEG;AACH,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAST"}
|
package/dist/utils.js
CHANGED
|
@@ -8,9 +8,12 @@ exports.validateBuildPath = validateBuildPath;
|
|
|
8
8
|
exports.getLatestBuildToolsVersions = getLatestBuildToolsVersions;
|
|
9
9
|
exports.longestDeterministicGroup = longestDeterministicGroup;
|
|
10
10
|
exports.basePath = basePath;
|
|
11
|
+
exports.isTracingEnabled = isTracingEnabled;
|
|
12
|
+
exports.shouldCaptureScreenshotsFromTrace = shouldCaptureScreenshotsFromTrace;
|
|
11
13
|
const test_1 = __importDefault(require("@playwright/test"));
|
|
12
14
|
const fs_1 = __importDefault(require("fs"));
|
|
13
15
|
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const visualTrace_1 = require("./visualTrace");
|
|
14
17
|
function boxedStep(target, context) {
|
|
15
18
|
return function replacementMethod(...args) {
|
|
16
19
|
const path = this.selector ? `("${this.selector}")` : "";
|
|
@@ -23,7 +26,47 @@ function boxedStep(target, context) {
|
|
|
23
26
|
: "";
|
|
24
27
|
const name = `${context.name}${path}${argsString}`;
|
|
25
28
|
return test_1.default.step(name, async () => {
|
|
26
|
-
|
|
29
|
+
let result;
|
|
30
|
+
let stepFailed = false;
|
|
31
|
+
try {
|
|
32
|
+
result = await target.call(this, ...args);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
stepFailed = true;
|
|
36
|
+
throw error; // Re-throw to preserve test failure
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
// Capture screenshot even if step throws an error
|
|
40
|
+
let visualTrace = (0, visualTrace_1.getVisualTraceService)();
|
|
41
|
+
// Check if we need to reinitialize for a new test (important for persistentDevice)
|
|
42
|
+
if (test_1.default.info) {
|
|
43
|
+
try {
|
|
44
|
+
const testInfo = test_1.default.info();
|
|
45
|
+
const device = this.device || this;
|
|
46
|
+
// Initialize or reinitialize if needed
|
|
47
|
+
if (device?.initializeVisualTrace && testInfo) {
|
|
48
|
+
const needsInit = (0, visualTrace_1.needsVisualTraceReinitialization)(testInfo.testId, testInfo.retry);
|
|
49
|
+
if (needsInit) {
|
|
50
|
+
// Get visual trace config from project
|
|
51
|
+
const visualTraceConfig = testInfo.project?.use
|
|
52
|
+
?.visualTrace;
|
|
53
|
+
device.initializeVisualTrace(testInfo, testInfo.retry, visualTraceConfig);
|
|
54
|
+
visualTrace = (0, visualTrace_1.getVisualTraceService)();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
// test.info() might not be available in some contexts
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// For Device methods, 'this' is the Device instance
|
|
63
|
+
// For Locator methods, 'this.device' is the Device instance
|
|
64
|
+
const takeScreenshot = this.device?.takeScreenshot || this.takeScreenshot;
|
|
65
|
+
if (visualTrace && takeScreenshot) {
|
|
66
|
+
await visualTrace.captureScreenshot(() => takeScreenshot.call(this.device || this), context.name, stepFailed);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
27
70
|
}, { box: true });
|
|
28
71
|
};
|
|
29
72
|
}
|
|
@@ -64,3 +107,40 @@ function longestDeterministicGroup(pattern) {
|
|
|
64
107
|
function basePath() {
|
|
65
108
|
return path_1.default.join(process.cwd(), "playwright-report", "data", "videos-store");
|
|
66
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Check if tracing is enabled for the current test based on Playwright's trace configuration
|
|
112
|
+
*/
|
|
113
|
+
function isTracingEnabled(testInfo, retryIndex) {
|
|
114
|
+
const traceMode = testInfo.project.use?.trace;
|
|
115
|
+
if (!traceMode || traceMode === "off") {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (traceMode === "on") {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
if (traceMode === "retain-on-failure") {
|
|
122
|
+
// Tracing is enabled if the test has failed
|
|
123
|
+
return testInfo.status !== undefined && testInfo.status !== "passed";
|
|
124
|
+
}
|
|
125
|
+
if (traceMode === "on-first-retry") {
|
|
126
|
+
// Tracing is enabled on the first retry only
|
|
127
|
+
return retryIndex === 1;
|
|
128
|
+
}
|
|
129
|
+
if (traceMode === "on-all-retries" || traceMode === "retry-with-trace") {
|
|
130
|
+
// Tracing is enabled on all retries
|
|
131
|
+
return retryIndex > 0;
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Determine if screenshots should be captured based on trace mode
|
|
137
|
+
*/
|
|
138
|
+
function shouldCaptureScreenshotsFromTrace(testInfo, retryIndex) {
|
|
139
|
+
// Check if screenshots are explicitly disabled in project config
|
|
140
|
+
const screenshotsConfig = testInfo.project.use?.screenshot;
|
|
141
|
+
if (screenshotsConfig === "off") {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
// Use trace configuration to determine screenshot behavior
|
|
145
|
+
return isTracingEnabled(testInfo, retryIndex);
|
|
146
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/visualTrace/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,gCAAgC,GACjC,MAAM,WAAW,CAAC;AAEnB,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.needsVisualTraceReinitialization = exports.clearVisualTraceService = exports.getVisualTraceService = exports.initializeVisualTrace = exports.VisualTraceService = void 0;
|
|
4
|
+
var service_1 = require("./service");
|
|
5
|
+
Object.defineProperty(exports, "VisualTraceService", { enumerable: true, get: function () { return service_1.VisualTraceService; } });
|
|
6
|
+
Object.defineProperty(exports, "initializeVisualTrace", { enumerable: true, get: function () { return service_1.initializeVisualTrace; } });
|
|
7
|
+
Object.defineProperty(exports, "getVisualTraceService", { enumerable: true, get: function () { return service_1.getVisualTraceService; } });
|
|
8
|
+
Object.defineProperty(exports, "clearVisualTraceService", { enumerable: true, get: function () { return service_1.clearVisualTraceService; } });
|
|
9
|
+
Object.defineProperty(exports, "needsVisualTraceReinitialization", { enumerable: true, get: function () { return service_1.needsVisualTraceReinitialization; } });
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { TestInfo } from "@playwright/test";
|
|
2
|
+
import { VisualTraceConfig } from "../types";
|
|
3
|
+
export interface VisualTraceState {
|
|
4
|
+
screenshotCount: number;
|
|
5
|
+
dedupeHashes: Set<string>;
|
|
6
|
+
}
|
|
7
|
+
export declare class VisualTraceService {
|
|
8
|
+
private states;
|
|
9
|
+
private testInfo;
|
|
10
|
+
readonly retryIndex: number;
|
|
11
|
+
private config;
|
|
12
|
+
readonly testId: string;
|
|
13
|
+
constructor(testInfo: TestInfo, retryIndex: number, config?: VisualTraceConfig);
|
|
14
|
+
/**
|
|
15
|
+
* Get or create state for the current test attempt
|
|
16
|
+
*/
|
|
17
|
+
private getState;
|
|
18
|
+
/**
|
|
19
|
+
* Check if screenshots should be captured based on trace mode and test status
|
|
20
|
+
*/
|
|
21
|
+
shouldCaptureScreenshot(stepFailed?: boolean): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Check our own config mode for screenshot capture
|
|
24
|
+
*/
|
|
25
|
+
private checkConfigMode;
|
|
26
|
+
/**
|
|
27
|
+
* Check if the current test is failing
|
|
28
|
+
*/
|
|
29
|
+
private isTestFailing;
|
|
30
|
+
/**
|
|
31
|
+
* Calculate hash for screenshot deduplication
|
|
32
|
+
*/
|
|
33
|
+
private calculateHash;
|
|
34
|
+
/**
|
|
35
|
+
* Check if we've reached the screenshot limit
|
|
36
|
+
*/
|
|
37
|
+
private hasReachedLimit;
|
|
38
|
+
/**
|
|
39
|
+
* Check if screenshot is a duplicate
|
|
40
|
+
*/
|
|
41
|
+
private isDuplicate;
|
|
42
|
+
/**
|
|
43
|
+
* Capture and attach a screenshot to the test
|
|
44
|
+
*/
|
|
45
|
+
captureScreenshot(takeScreenshot: () => Promise<Buffer>, stepTitle?: string, stepFailed?: boolean): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Reset state for a test (useful for cleanup)
|
|
48
|
+
*/
|
|
49
|
+
resetState(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Get current screenshot count for the test
|
|
52
|
+
*/
|
|
53
|
+
getScreenshotCount(): number;
|
|
54
|
+
/**
|
|
55
|
+
* Update configuration at runtime
|
|
56
|
+
*/
|
|
57
|
+
updateConfig(config: Partial<VisualTraceConfig>): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Initialize the Visual Trace Service for a test
|
|
61
|
+
*/
|
|
62
|
+
export declare function initializeVisualTrace(testInfo: TestInfo, retryIndex: number, config?: VisualTraceConfig): VisualTraceService;
|
|
63
|
+
/**
|
|
64
|
+
* Get the current Visual Trace Service instance
|
|
65
|
+
*/
|
|
66
|
+
export declare function getVisualTraceService(): VisualTraceService | null;
|
|
67
|
+
/**
|
|
68
|
+
* Check if the Visual Trace Service needs reinitialization for a different test
|
|
69
|
+
*/
|
|
70
|
+
export declare function needsVisualTraceReinitialization(testId: string, retryIndex: number): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Clear the Visual Trace Service instance
|
|
73
|
+
*/
|
|
74
|
+
export declare function clearVisualTraceService(): void;
|
|
75
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/visualTrace/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC3B;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,QAAQ,CAAW;IAC3B,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,OAAO,CAAC,MAAM,CAAoB;IAClC,SAAgB,MAAM,EAAE,MAAM,CAAC;gBAG7B,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,iBAAiB;IAc5B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAahB;;OAEG;IACH,uBAAuB,CAAC,UAAU,GAAE,OAAe,GAAG,OAAO;IA6D7D;;OAEG;IACH,OAAO,CAAC,eAAe;IAevB;;OAEG;IACH,OAAO,CAAC,aAAa;IAgBrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB;;OAEG;IACH,OAAO,CAAC,WAAW;IAgBnB;;OAEG;IACG,iBAAiB,CACrB,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EACrC,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,GAAE,OAAe,GAC1B,OAAO,CAAC,IAAI,CAAC;IA0ChB;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAK5B;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI;CAGvD;AAKD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,iBAAiB,GACzB,kBAAkB,CAGpB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,kBAAkB,GAAG,IAAI,CAEjE;AAED;;GAEG;AACH,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAST;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAK9C"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.VisualTraceService = void 0;
|
|
7
|
+
exports.initializeVisualTrace = initializeVisualTrace;
|
|
8
|
+
exports.getVisualTraceService = getVisualTraceService;
|
|
9
|
+
exports.needsVisualTraceReinitialization = needsVisualTraceReinitialization;
|
|
10
|
+
exports.clearVisualTraceService = clearVisualTraceService;
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
class VisualTraceService {
|
|
13
|
+
states = new Map();
|
|
14
|
+
testInfo;
|
|
15
|
+
retryIndex;
|
|
16
|
+
config;
|
|
17
|
+
testId;
|
|
18
|
+
constructor(testInfo, retryIndex, config) {
|
|
19
|
+
this.testInfo = testInfo;
|
|
20
|
+
this.retryIndex = retryIndex;
|
|
21
|
+
this.testId = testInfo.testId;
|
|
22
|
+
// Default configuration: only capture on failure
|
|
23
|
+
this.config = {
|
|
24
|
+
enableScreenshots: config?.enableScreenshots ?? "retain-on-failure",
|
|
25
|
+
maxScreenshots: config?.maxScreenshots ?? 50,
|
|
26
|
+
dedupe: config?.dedupe ?? true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get or create state for the current test attempt
|
|
31
|
+
*/
|
|
32
|
+
getState() {
|
|
33
|
+
const key = `${this.testInfo.testId}#${this.retryIndex}`;
|
|
34
|
+
if (!this.states.has(key)) {
|
|
35
|
+
this.states.set(key, {
|
|
36
|
+
screenshotCount: 0,
|
|
37
|
+
dedupeHashes: new Set(),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return this.states.get(key);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if screenshots should be captured based on trace mode and test status
|
|
44
|
+
*/
|
|
45
|
+
shouldCaptureScreenshot(stepFailed = false) {
|
|
46
|
+
// Check if screenshots are explicitly disabled
|
|
47
|
+
if (this.config.enableScreenshots === false ||
|
|
48
|
+
this.config.enableScreenshots === "off") {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
// Get trace configuration from Playwright
|
|
52
|
+
const traceConfig = this.testInfo.project.use?.trace;
|
|
53
|
+
// If no trace mode is set, use our config
|
|
54
|
+
if (!traceConfig) {
|
|
55
|
+
return this.checkConfigMode(stepFailed);
|
|
56
|
+
}
|
|
57
|
+
// Handle both string and object trace configurations
|
|
58
|
+
let traceMode;
|
|
59
|
+
if (typeof traceConfig === "string") {
|
|
60
|
+
traceMode = traceConfig;
|
|
61
|
+
}
|
|
62
|
+
else if (typeof traceConfig === "object" && traceConfig.mode) {
|
|
63
|
+
// Handle object configuration like { mode: 'on', screenshots: true }
|
|
64
|
+
traceMode = traceConfig.mode;
|
|
65
|
+
// If screenshots are explicitly disabled in trace config, respect that
|
|
66
|
+
if (traceConfig.screenshots === false) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Unknown format, fall back to our config
|
|
72
|
+
return this.checkConfigMode(stepFailed);
|
|
73
|
+
}
|
|
74
|
+
// Map Playwright trace modes to screenshot behavior
|
|
75
|
+
switch (traceMode) {
|
|
76
|
+
case "on":
|
|
77
|
+
return true;
|
|
78
|
+
case "retain-on-failure":
|
|
79
|
+
// Capture if step failed, test already failed, or we're in a retry
|
|
80
|
+
return stepFailed || this.isTestFailing() || this.retryIndex > 0;
|
|
81
|
+
case "on-first-retry":
|
|
82
|
+
// Only capture on first retry
|
|
83
|
+
return this.retryIndex === 1;
|
|
84
|
+
case "on-all-retries":
|
|
85
|
+
// Capture on all retries
|
|
86
|
+
return this.retryIndex > 0;
|
|
87
|
+
case "retry-with-trace":
|
|
88
|
+
return this.retryIndex > 0;
|
|
89
|
+
case "off":
|
|
90
|
+
return false;
|
|
91
|
+
default:
|
|
92
|
+
return this.checkConfigMode(stepFailed);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check our own config mode for screenshot capture
|
|
97
|
+
*/
|
|
98
|
+
checkConfigMode(stepFailed = false) {
|
|
99
|
+
const mode = this.config.enableScreenshots;
|
|
100
|
+
if (mode === true || mode === "on") {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (mode === "retain-on-failure") {
|
|
104
|
+
// Capture if step failed or test is already failing
|
|
105
|
+
return stepFailed || this.isTestFailing();
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Check if the current test is failing
|
|
111
|
+
*/
|
|
112
|
+
isTestFailing() {
|
|
113
|
+
// Check test status (will be 'failed', 'timedOut', etc. for failures)
|
|
114
|
+
if (this.testInfo.status && this.testInfo.status !== "passed") {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
// Check if there are any errors
|
|
118
|
+
if (this.testInfo.errors && this.testInfo.errors.length > 0) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
// During test execution, we might not have status yet
|
|
122
|
+
// This will be handled by the retry detection
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Calculate hash for screenshot deduplication
|
|
127
|
+
*/
|
|
128
|
+
calculateHash(data) {
|
|
129
|
+
return crypto_1.default.createHash("sha256").update(data).digest("hex");
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check if we've reached the screenshot limit
|
|
133
|
+
*/
|
|
134
|
+
hasReachedLimit() {
|
|
135
|
+
const state = this.getState();
|
|
136
|
+
return !!(this.config.maxScreenshots &&
|
|
137
|
+
state.screenshotCount >= this.config.maxScreenshots);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if screenshot is a duplicate
|
|
141
|
+
*/
|
|
142
|
+
isDuplicate(data) {
|
|
143
|
+
if (!this.config.dedupe) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const state = this.getState();
|
|
147
|
+
const hash = this.calculateHash(data);
|
|
148
|
+
if (state.dedupeHashes.has(hash)) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
state.dedupeHashes.add(hash);
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Capture and attach a screenshot to the test
|
|
156
|
+
*/
|
|
157
|
+
async captureScreenshot(takeScreenshot, stepTitle, stepFailed = false) {
|
|
158
|
+
// Check if we should capture screenshots
|
|
159
|
+
if (!this.shouldCaptureScreenshot(stepFailed)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// Check if we've reached the screenshot limit
|
|
163
|
+
if (this.hasReachedLimit()) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
// Take the screenshot
|
|
168
|
+
const screenshotData = await takeScreenshot();
|
|
169
|
+
// Check if this is a duplicate screenshot
|
|
170
|
+
if (this.isDuplicate(screenshotData)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// Update state
|
|
174
|
+
const state = this.getState();
|
|
175
|
+
state.screenshotCount++;
|
|
176
|
+
// Generate filename with step title if provided
|
|
177
|
+
const timestamp = Date.now();
|
|
178
|
+
const stepSuffix = stepTitle
|
|
179
|
+
? `-${stepTitle.replace(/[^a-zA-Z0-9]/g, "_")}`
|
|
180
|
+
: "";
|
|
181
|
+
const filename = `screenshot-${timestamp}${stepSuffix}.png`;
|
|
182
|
+
// Attach to test
|
|
183
|
+
await this.testInfo.attach(filename, {
|
|
184
|
+
body: screenshotData,
|
|
185
|
+
contentType: "image/png",
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
// Log error but don't fail the test
|
|
190
|
+
console.warn(`Failed to capture screenshot: ${error}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Reset state for a test (useful for cleanup)
|
|
195
|
+
*/
|
|
196
|
+
resetState() {
|
|
197
|
+
const key = `${this.testInfo.testId}#${this.retryIndex}`;
|
|
198
|
+
this.states.delete(key);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get current screenshot count for the test
|
|
202
|
+
*/
|
|
203
|
+
getScreenshotCount() {
|
|
204
|
+
const state = this.getState();
|
|
205
|
+
return state.screenshotCount;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Update configuration at runtime
|
|
209
|
+
*/
|
|
210
|
+
updateConfig(config) {
|
|
211
|
+
this.config = { ...this.config, ...config };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
exports.VisualTraceService = VisualTraceService;
|
|
215
|
+
// Singleton instance management
|
|
216
|
+
let serviceInstance = null;
|
|
217
|
+
/**
|
|
218
|
+
* Initialize the Visual Trace Service for a test
|
|
219
|
+
*/
|
|
220
|
+
function initializeVisualTrace(testInfo, retryIndex, config) {
|
|
221
|
+
serviceInstance = new VisualTraceService(testInfo, retryIndex, config);
|
|
222
|
+
return serviceInstance;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get the current Visual Trace Service instance
|
|
226
|
+
*/
|
|
227
|
+
function getVisualTraceService() {
|
|
228
|
+
return serviceInstance;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Check if the Visual Trace Service needs reinitialization for a different test
|
|
232
|
+
*/
|
|
233
|
+
function needsVisualTraceReinitialization(testId, retryIndex) {
|
|
234
|
+
if (!serviceInstance) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
// Reinitialize if it's a different test or retry
|
|
238
|
+
return (serviceInstance.testId !== testId ||
|
|
239
|
+
serviceInstance.retryIndex !== retryIndex);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Clear the Visual Trace Service instance
|
|
243
|
+
*/
|
|
244
|
+
function clearVisualTraceService() {
|
|
245
|
+
if (serviceInstance) {
|
|
246
|
+
serviceInstance.resetState();
|
|
247
|
+
serviceInstance = null;
|
|
248
|
+
}
|
|
249
|
+
}
|