@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 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
 
@@ -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 path_1 = __importDefault(require("path"));
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;
@@ -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 os = __importStar(require("os"));
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.stdoutData = '';
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 applitoolsIdentifierAndFolder = result.attachments[index].body.toString();
62
- this.applitoolsIdentifiersAndFolders.push(applitoolsIdentifierAndFolder);
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 applitoolsIdentifierAndFolder of this.applitoolsIdentifiersAndFolders) {
83
- const content = await exports.InternalData.consume(applitoolsIdentifierAndFolder, this.stdoutData);
84
- if (content) {
85
- testResultsMap[content.key] = content.data;
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 defaultDataDirectory = path.join(os.tmpdir(), 'eyes-playwright-data');
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
- determineDirectory(testInfo) {
102
- var _a;
103
- const blobReporter = testInfo.config.reporter.find(r => r[0].includes('blob'));
104
- const customOutputDir = (_a = blobReporter === null || blobReporter === void 0 ? void 0 : blobReporter[1]) === null || _a === void 0 ? void 0 : _a.outputDir;
105
- return customOutputDir ? path.join(customOutputDir, 'eyes-report-files') : defaultDataDirectory;
106
- },
107
- hasBlobReporter(testInfo) {
108
- return testInfo.config.reporter.some(r => r[0].includes('blob'));
109
- },
110
- async write({ testInfo, data }) {
111
- const [applitoolsIdentifier, folder] = testInfo.attachments
112
- .find(a => a.name === 'applitoolsIdentifier')
113
- .body.toString()
114
- .split('|')
115
- .map(s => s.trim())
116
- .filter(Boolean); // if the directory is not specified, it will be empty
117
- const key = `${testInfo.testId}--${testInfo.retry}`;
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(applitoolsIdentifierAndFolder, stdoutData) {
131
- const [name, folder] = applitoolsIdentifierAndFolder
132
- .split('|')
133
- .map(s => s.trim())
134
- .filter(Boolean); // if the directory is not specified, it will be empty
135
- // Try stdout data first (for blob reporter scenarios), then fall back to files
136
- if (stdoutData) {
137
- // Parse from stdout for blob reporter scenarios
138
- const results = this.parseStdoutLine(name, stdoutData);
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 parsed = JSON.parse(jsonPart);
163
- results.push(parsed);
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 (err) {
166
- // Skip invalid JSON lines
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.0",
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
  },