@applitools/eyes-playwright 1.39.0 → 1.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/fixture/index.js +9 -44
- package/dist/fixture/reporter.js +114 -87
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.39.1](https://github.com/Applitools-Dev/sdk/compare/js/eyes-playwright@1.39.0...js/eyes-playwright@1.39.1) (2025-08-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* save pw reporter temp files using the backend | FLD-3427 ([#3132](https://github.com/Applitools-Dev/sdk/issues/3132)) ([5d3e756](https://github.com/Applitools-Dev/sdk/commit/5d3e756aea742b1758032037f56d62a932c805a2))
|
|
9
|
+
|
|
3
10
|
## [1.39.0](https://github.com/Applitools-Dev/sdk/compare/js/eyes-playwright@1.38.2...js/eyes-playwright@1.39.0) (2025-08-05)
|
|
4
11
|
|
|
5
12
|
|
package/dist/fixture/index.js
CHANGED
|
@@ -1,30 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
3
|
exports.test = exports.expect = void 0;
|
|
30
4
|
/* eslint-disable no-console */
|
|
@@ -33,10 +7,9 @@ const test_1 = require("@playwright/test");
|
|
|
33
7
|
const getEyes_1 = require("./getEyes");
|
|
34
8
|
var toHaveScreenshot_1 = require("./toHaveScreenshot");
|
|
35
9
|
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return toHaveScreenshot_1.expect; } });
|
|
36
|
-
const utils = __importStar(require("@applitools/utils"));
|
|
37
10
|
const reporter_1 = require("./reporter");
|
|
38
11
|
const url_1 = require("url");
|
|
39
|
-
const
|
|
12
|
+
const utils_1 = require("@applitools/utils");
|
|
40
13
|
exports.test = test_1.test.extend({
|
|
41
14
|
eyesConfig: [{}, { option: true }],
|
|
42
15
|
eyesRunner: [
|
|
@@ -89,27 +62,15 @@ exports.test = test_1.test.extend({
|
|
|
89
62
|
],
|
|
90
63
|
closeEyesIfNeeded: [
|
|
91
64
|
async ({ page, eyesConfig, eyesRunner: runner }, use, testInfo) => {
|
|
92
|
-
var _a, _b, _c, _d, _e, _f;
|
|
93
65
|
const isUsingEyesReporter = testInfo.config.reporter.some(
|
|
94
66
|
// typeof testInfo.config.reporter is [string, ReporterOptions][], that's why [0] is the reporter name
|
|
95
67
|
r => r[0].includes('eyes-playwright') || r[0].includes('blob'));
|
|
96
|
-
const blobSettings = (_b = (_a = testInfo.config.reporter.find(r => r[0].includes('blob'))) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : {};
|
|
97
|
-
const blobFolder = (_d = (_c = blobSettings.outputDir) !== null && _c !== void 0 ? _c : process.env.PLAYWRIGHT_BLOB_OUTPUT_DIR) !== null && _d !== void 0 ? _d : (((_e = process.env.PLAYWRIGHT_BLOB_OUTPUT_FILE) !== null && _e !== void 0 ? _e : blobSettings.outputFile)
|
|
98
|
-
? path_1.default.basename((_f = process.env.PLAYWRIGHT_BLOB_OUTPUT_FILE) !== null && _f !== void 0 ? _f : blobSettings.outputFile)
|
|
99
|
-
: '');
|
|
100
|
-
if (isUsingEyesReporter) {
|
|
101
|
-
const applitoolsIdentifier = utils.general.guid();
|
|
102
|
-
// Create an identifier that will be collected by the html or reporter
|
|
103
|
-
await testInfo.attach('applitoolsIdentifier', {
|
|
104
|
-
body: `${applitoolsIdentifier}|${blobFolder}`,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
68
|
await use();
|
|
108
69
|
const eyes = page.__eyes;
|
|
109
70
|
if (eyes) {
|
|
110
71
|
await eyes.closeAsync();
|
|
111
72
|
if (isUsingEyesReporter) {
|
|
112
|
-
writeResultsWhenReady({ eyes, runner, testInfo });
|
|
73
|
+
await writeResultsWhenReady({ eyes, runner, testInfo });
|
|
113
74
|
}
|
|
114
75
|
if (eyesConfig.failTestsOnDiff === 'afterEach') {
|
|
115
76
|
await eyes.getResults(true);
|
|
@@ -131,13 +92,17 @@ function outputDigest(workerInfo, results) {
|
|
|
131
92
|
function output(...args) {
|
|
132
93
|
console.log('👀', ...args);
|
|
133
94
|
}
|
|
134
|
-
function writeResultsWhenReady({ eyes, runner, testInfo }) {
|
|
135
|
-
var _a;
|
|
95
|
+
async function writeResultsWhenReady({ eyes, runner, testInfo }) {
|
|
96
|
+
var _a, _b;
|
|
136
97
|
const fixturePromises = ((_a = runner.fixturePromises) !== null && _a !== void 0 ? _a : (runner.fixturePromises = []));
|
|
98
|
+
const uuid = utils_1.general.guid();
|
|
99
|
+
await testInfo.attach('applitoolsIdentifier', {
|
|
100
|
+
body: `${uuid}|${(_b = eyes.getServerUrl()) !== null && _b !== void 0 ? _b : 'https://eyes.applitools.com'}|${eyes.getApiKey()}`,
|
|
101
|
+
});
|
|
137
102
|
fixturePromises.push(eyes
|
|
138
103
|
.getResults(false)
|
|
139
104
|
.then(async (testResults) => {
|
|
140
|
-
await reporter_1.InternalData.write({ testInfo, data: testResults });
|
|
105
|
+
await reporter_1.InternalData.write({ testInfo, data: testResults, eyes, uuid });
|
|
141
106
|
})
|
|
142
107
|
.catch((e) => {
|
|
143
108
|
var _a;
|
package/dist/fixture/reporter.js
CHANGED
|
@@ -26,7 +26,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
26
|
exports.InternalData = void 0;
|
|
27
27
|
const fs = __importStar(require("fs"));
|
|
28
28
|
const path = __importStar(require("path"));
|
|
29
|
-
const
|
|
29
|
+
const req_1 = require("@applitools/req");
|
|
30
|
+
const utils = __importStar(require("@applitools/utils"));
|
|
31
|
+
const logger_1 = require("@applitools/logger");
|
|
32
|
+
const storage_blob_1 = require("@azure/storage-blob");
|
|
30
33
|
const playwrightPath = require.resolve('playwright');
|
|
31
34
|
const HtmlReporter = require(path.join(path.dirname(playwrightPath), '/lib/reporters/html')).default;
|
|
32
35
|
const pluginsFile = require.resolve('../../dist/fixture/reportRenderer.js');
|
|
@@ -46,20 +49,15 @@ const createInjectedScript = (testResultsMap) => `
|
|
|
46
49
|
class EyesReporter extends HtmlReporter {
|
|
47
50
|
constructor(options) {
|
|
48
51
|
super(options);
|
|
49
|
-
this.
|
|
50
|
-
this.applitoolsIdentifiersAndFolders = [];
|
|
51
|
-
}
|
|
52
|
-
onStdOut(chunk) {
|
|
53
|
-
// Capture stdout data for blob reporter scenarios
|
|
54
|
-
this.stdoutData += chunk.toString();
|
|
52
|
+
this.applitoolsIdentifiers = [];
|
|
55
53
|
}
|
|
56
54
|
onTestEnd(test, result) {
|
|
57
55
|
var _a;
|
|
58
56
|
(_a = super.onTestEnd) === null || _a === void 0 ? void 0 : _a.call(this, test, result);
|
|
59
57
|
const index = result.attachments.findIndex(a => a.name === 'applitoolsIdentifier');
|
|
60
58
|
if (index > -1) {
|
|
61
|
-
const
|
|
62
|
-
this.
|
|
59
|
+
const applitoolsIdentifier = result.attachments[index].body.toString();
|
|
60
|
+
this.applitoolsIdentifiers.push(applitoolsIdentifier);
|
|
63
61
|
this.removeInternalIdAttachment(result);
|
|
64
62
|
}
|
|
65
63
|
}
|
|
@@ -79,10 +77,20 @@ class EyesReporter extends HtmlReporter {
|
|
|
79
77
|
async onEnd(result) {
|
|
80
78
|
await super.onEnd(result);
|
|
81
79
|
const testResultsMap = {};
|
|
82
|
-
for (const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
for (const applitoolsIdentifier of this.applitoolsIdentifiers) {
|
|
81
|
+
// Parse the identifier format: uuid|serverUrl|apiKey
|
|
82
|
+
const [uuid, serverUrl, apiKey] = applitoolsIdentifier.split('|');
|
|
83
|
+
if (uuid && serverUrl && apiKey) {
|
|
84
|
+
// Create a minimal eyes object for the consume call
|
|
85
|
+
const eyes = {
|
|
86
|
+
getServerUrl: () => serverUrl,
|
|
87
|
+
getApiKey: () => apiKey,
|
|
88
|
+
// No getLogger method - will use makeLogger() fallback
|
|
89
|
+
};
|
|
90
|
+
const content = await exports.InternalData.consume(uuid, eyes);
|
|
91
|
+
if (content) {
|
|
92
|
+
testResultsMap[content.key] = content.data;
|
|
93
|
+
}
|
|
86
94
|
}
|
|
87
95
|
}
|
|
88
96
|
try {
|
|
@@ -96,85 +104,104 @@ class EyesReporter extends HtmlReporter {
|
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
exports.default = EyesReporter;
|
|
99
|
-
const
|
|
107
|
+
const getLogger = utils.general.cachify((settings) => {
|
|
108
|
+
return settings.logger || (0, logger_1.makeLogger)({ level: 'info' });
|
|
109
|
+
}, ([settings]) => settings.serverUrl + settings.apiKey);
|
|
110
|
+
const makeReporterRequest = (settings) => {
|
|
111
|
+
var _a;
|
|
112
|
+
const logger = getLogger(settings);
|
|
113
|
+
const req = (0, req_1.makeReq)({
|
|
114
|
+
baseUrl: (_a = settings.serverUrl) !== null && _a !== void 0 ? _a : 'https://eyes.applitools.com',
|
|
115
|
+
headers: {
|
|
116
|
+
Accept: 'application/json',
|
|
117
|
+
'Content-Type': 'application/json',
|
|
118
|
+
'x-applitools-eyes-client': 'eyes-playwright-reporter',
|
|
119
|
+
'User-Agent': 'eyes-playwright-reporter',
|
|
120
|
+
'X-Eyes-Api-Key': settings.apiKey,
|
|
121
|
+
},
|
|
122
|
+
retry: [
|
|
123
|
+
{
|
|
124
|
+
limit: 3,
|
|
125
|
+
timeout: 200,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
hooks: {
|
|
129
|
+
beforeRequest: request => {
|
|
130
|
+
var _a;
|
|
131
|
+
logger.log(`Making request to ${request.request.url} with body: ${(_a = request.options) === null || _a === void 0 ? void 0 : _a.body}`);
|
|
132
|
+
},
|
|
133
|
+
afterResponse: async (response) => {
|
|
134
|
+
logger.log(`Received response from ${response.response.url} with status ${response.response.status} and body: ${await response.response.clone().text()}`);
|
|
135
|
+
if (response.response.status >= 400) {
|
|
136
|
+
logger.error(`Request to ${response.response.url} failed with status ${response.response.status}`, `and body: ${await response.response.clone().text()}`);
|
|
137
|
+
throw new Error(`Request to ${response.response.url} failed with status ${response.response.status}`);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
upload,
|
|
144
|
+
download,
|
|
145
|
+
};
|
|
146
|
+
async function upload(uuid, data) {
|
|
147
|
+
const blobClient = new storage_blob_1.BlockBlobClient(settings.uploadUrl);
|
|
148
|
+
const [response1, response2] = await Promise.all([
|
|
149
|
+
req(`/batch/${uuid}/report/${uuid}`, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
}),
|
|
152
|
+
blobClient.upload(JSON.stringify(data), JSON.stringify(data).length),
|
|
153
|
+
]);
|
|
154
|
+
return {
|
|
155
|
+
respCreateUpload: await response1.json(),
|
|
156
|
+
respUpload: JSON.stringify(response2),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async function download(uuid) {
|
|
160
|
+
const response = await req(`/batch/${uuid}/report/${uuid}`, {
|
|
161
|
+
method: 'GET',
|
|
162
|
+
});
|
|
163
|
+
return response.json();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
100
166
|
exports.InternalData = {
|
|
101
|
-
|
|
102
|
-
var _a;
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (this.hasBlobReporter(testInfo)) {
|
|
119
|
-
// Use stdout approach for blob reporter scenarios
|
|
120
|
-
// eslint-disable-next-line no-console
|
|
121
|
-
console.log(`EYES_DATA:${applitoolsIdentifier}:${JSON.stringify({ key, data })}`);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
// Use file approach for direct reporter scenarios
|
|
125
|
-
const targetFolder = folder || this.determineDirectory(testInfo);
|
|
126
|
-
this.ensureFolder(targetFolder);
|
|
127
|
-
await fs.promises.writeFile(this.getPathForInternalId(applitoolsIdentifier, targetFolder), JSON.stringify({ key, data }));
|
|
128
|
-
}
|
|
167
|
+
async write({ testInfo, data, eyes, uuid }) {
|
|
168
|
+
var _a, _b;
|
|
169
|
+
const logger = ((_a = eyes.getLogger) === null || _a === void 0 ? void 0 : _a.call(eyes)) || (0, logger_1.makeLogger)();
|
|
170
|
+
const uploadUrl = eyes._eyes.test.account.batchExecReportsUrl
|
|
171
|
+
.replace('__batch_id__', uuid)
|
|
172
|
+
.replace('__report_id__', uuid);
|
|
173
|
+
const { upload } = makeReporterRequest({
|
|
174
|
+
serverUrl: (_b = eyes.getServerUrl()) !== null && _b !== void 0 ? _b : 'https://eyes.applitools.com',
|
|
175
|
+
apiKey: eyes.getApiKey(),
|
|
176
|
+
uploadUrl,
|
|
177
|
+
logger: logger,
|
|
178
|
+
});
|
|
179
|
+
const response = await upload(uuid, {
|
|
180
|
+
key: `${testInfo.testId}--${testInfo.retry}`,
|
|
181
|
+
data,
|
|
182
|
+
});
|
|
183
|
+
logger.log('Data written successfully:', response);
|
|
129
184
|
},
|
|
130
|
-
async consume(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// Return the last result if multiple exist, or undefined if none
|
|
140
|
-
return results.length > 0 ? results[results.length - 1] : undefined;
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
// Read from file for direct reporter scenarios
|
|
144
|
-
const targetFolder = folder || defaultDataDirectory;
|
|
145
|
-
const filepath = this.getPathForInternalId(name, targetFolder);
|
|
146
|
-
try {
|
|
147
|
-
const content = await fs.promises.readFile(filepath, 'utf-8').then(JSON.parse);
|
|
148
|
-
return content;
|
|
149
|
-
}
|
|
150
|
-
catch (err) {
|
|
151
|
-
// TODO no eyes results for test - this is ok, could be that no visual tests occurred for this playwright test
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
parseStdoutLine(identifier, stdoutData) {
|
|
156
|
-
const lines = stdoutData.split('\n');
|
|
157
|
-
const eyesLines = lines.filter(line => line.startsWith(`EYES_DATA:${identifier}:`));
|
|
158
|
-
const results = [];
|
|
159
|
-
for (const eyesLine of eyesLines) {
|
|
160
|
-
const jsonPart = eyesLine.replace(`EYES_DATA:${identifier}:`, '');
|
|
185
|
+
async consume(uuid, eyes) {
|
|
186
|
+
var _a;
|
|
187
|
+
const logger = ((_a = eyes.getLogger) === null || _a === void 0 ? void 0 : _a.call(eyes)) || (0, logger_1.makeLogger)();
|
|
188
|
+
const { download } = makeReporterRequest({
|
|
189
|
+
serverUrl: eyes.getServerUrl(),
|
|
190
|
+
apiKey: eyes.getApiKey(),
|
|
191
|
+
logger: logger,
|
|
192
|
+
});
|
|
193
|
+
for (let retry = 0; retry < 3; retry++) {
|
|
161
194
|
try {
|
|
162
|
-
const
|
|
163
|
-
|
|
195
|
+
const response = await download(uuid);
|
|
196
|
+
if (utils.types.has(response, 'status') && response.status !== 200) {
|
|
197
|
+
throw new Error(`Failed to consume data for UUID ${uuid}: ${response.status} - ${JSON.stringify(response)}`);
|
|
198
|
+
}
|
|
199
|
+
return { key: response.key, data: response.data };
|
|
164
200
|
}
|
|
165
|
-
catch (
|
|
166
|
-
|
|
167
|
-
continue;
|
|
201
|
+
catch (error) {
|
|
202
|
+
logger.warn('Failed to consume data:', error, ". Perhaps the test doesn't include Applitools tests?");
|
|
168
203
|
}
|
|
204
|
+
await utils.general.sleep(1000 * (retry + 1)); // the backend might need a few seconds to process the upload
|
|
169
205
|
}
|
|
170
|
-
return results;
|
|
171
|
-
},
|
|
172
|
-
ensureFolder(folderPath = defaultDataDirectory) {
|
|
173
|
-
if (!fs.existsSync(folderPath)) {
|
|
174
|
-
fs.mkdirSync(folderPath, { recursive: true });
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
getPathForInternalId(applitoolsIdentifier, folderPath = defaultDataDirectory) {
|
|
178
|
-
return path.join(folderPath, applitoolsIdentifier);
|
|
179
206
|
},
|
|
180
207
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applitools/eyes-playwright",
|
|
3
|
-
"version": "1.39.
|
|
3
|
+
"version": "1.39.1",
|
|
4
4
|
"description": "Applitools Eyes SDK for Playwright",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eyes-playwright",
|
|
@@ -60,8 +60,10 @@
|
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@applitools/eyes": "1.36.0",
|
|
63
|
+
"@applitools/req": "1.8.1",
|
|
63
64
|
"@applitools/spec-driver-playwright": "1.7.1",
|
|
64
65
|
"@applitools/utils": "1.11.0",
|
|
66
|
+
"@azure/storage-blob": "^12.28.0",
|
|
65
67
|
"@inquirer/prompts": "7.0.1",
|
|
66
68
|
"chalk": "4.1.2",
|
|
67
69
|
"yargs": "17.7.2"
|
|
@@ -77,6 +79,7 @@
|
|
|
77
79
|
"@rollup/plugin-typescript": "^12.1.0",
|
|
78
80
|
"@types/node": "^12.20.55",
|
|
79
81
|
"jszip": "^3.10.1",
|
|
82
|
+
"nock": "^13.3.2",
|
|
80
83
|
"playwright": "1.49.0",
|
|
81
84
|
"rollup": "^4.1.4"
|
|
82
85
|
},
|