@applitools/eyes-playwright
Version:
Applitools Eyes SDK for Playwright
208 lines (207 loc) • 9.35 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InternalData = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const req_1 = require("@applitools/req");
const utils = __importStar(require("@applitools/utils"));
const logger_1 = require("@applitools/logger");
const storage_blob_1 = require("@azure/storage-blob");
const playwrightPath = require.resolve('playwright');
const HtmlReporter = require(path.join(path.dirname(playwrightPath), '/lib/reporters/html')).default;
const pluginsFile = require.resolve('../../dist/fixture/reportRenderer.js');
const styleFile = require.resolve('./reporterStyle.css');
const createInjectedScript = (testResultsMap) => `
<script>window.__icons = {
visualTest: \`${fs.readFileSync(require.resolve('./images/visual-text.svg'), 'utf-8')}\`,
link: \`${fs.readFileSync(require.resolve('./images/link.svg'), 'utf-8')}\`,
}</script>
<script>
${fs.readFileSync(pluginsFile, 'utf-8')}
window.__testResultsMap = ${JSON.stringify(testResultsMap)};
window.__initEyesReport();
</script>
<style type="text/css">${fs.readFileSync(styleFile, 'utf-8')}</style>
`;
class EyesReporter extends HtmlReporter {
constructor(options) {
super(options);
this.applitoolsIdentifiers = [];
}
onTestEnd(test, result) {
var _a;
(_a = super.onTestEnd) === null || _a === void 0 ? void 0 : _a.call(this, test, result);
const index = result.attachments.findIndex(a => a.name === 'applitoolsIdentifier');
if (index > -1) {
const applitoolsIdentifier = result.attachments[index].body.toString();
this.applitoolsIdentifiers.push(applitoolsIdentifier);
this.removeInternalIdAttachment(result);
}
}
// TODO: let's remove this method! Removing the internal id from the test result is wrong,
// it should be hidden in the HTML report and the test result should be read-only.
removeInternalIdAttachment(result) {
var _a, _b, _c;
const index = result.attachments ? result.attachments.findIndex(a => (a === null || a === void 0 ? void 0 : a.name) === 'applitoolsIdentifier') : -1;
if (index > -1)
(_a = result.attachments) === null || _a === void 0 ? void 0 : _a.splice(index, 1);
const index2 = result.attachments ? result.attachments.findIndex(a => !a) : -1;
if (index2 > -1)
(_c = (_b = result._endPayload) === null || _b === void 0 ? void 0 : _b.attachments) === null || _c === void 0 ? void 0 : _c.splice(index2, 1);
if (result.steps)
result.steps.forEach(step => this.removeInternalIdAttachment(step));
}
async onEnd(result) {
await super.onEnd(result);
const testResultsMap = {};
for (const applitoolsIdentifier of this.applitoolsIdentifiers) {
// Parse the identifier format: uuid|serverUrl|apiKey
const [uuid, serverUrl, apiKey] = applitoolsIdentifier.split('|');
if (uuid && serverUrl && apiKey) {
// Create a minimal eyes object for the consume call
const eyes = {
getServerUrl: () => serverUrl,
getApiKey: () => apiKey,
// No getLogger method - will use makeLogger() fallback
};
const content = await exports.InternalData.consume(uuid, eyes);
if (content) {
testResultsMap[content.key] = content.data;
}
}
}
try {
const filePath = path.join(this._outputFolder, 'index.html');
fs.appendFileSync(filePath, createInjectedScript(testResultsMap));
}
catch (e) {
// eslint-disable-next-line no-console
console.error('Error modifying index.html:', e);
}
}
}
exports.default = EyesReporter;
const getLogger = utils.general.cachify((settings) => {
return settings.logger || (0, logger_1.makeLogger)({ level: 'info' });
}, ([settings]) => settings.serverUrl + settings.apiKey);
const makeReporterRequest = (settings) => {
var _a;
const logger = getLogger(settings);
const req = (0, req_1.makeReq)({
baseUrl: (_a = settings.serverUrl) !== null && _a !== void 0 ? _a : 'https://eyes.applitools.com',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'x-applitools-eyes-client': 'eyes-playwright-reporter',
'User-Agent': 'eyes-playwright-reporter',
'X-Eyes-Api-Key': settings.apiKey,
},
retry: [
{
limit: 3,
timeout: 200,
},
],
hooks: {
beforeRequest: request => {
var _a;
logger.log(`Making request to ${request.request.url} with body: ${(_a = request.options) === null || _a === void 0 ? void 0 : _a.body}`);
},
afterResponse: async (response) => {
logger.log(`Received response from ${response.response.url} with status ${response.response.status} and body: ${await response.response.clone().text()}`);
if (response.response.status >= 400) {
logger.error(`Request to ${response.response.url} failed with status ${response.response.status}`, `and body: ${await response.response.clone().text()}`);
throw new Error(`Request to ${response.response.url} failed with status ${response.response.status}`);
}
},
},
});
return {
upload,
download,
};
async function upload(uuid, data) {
const blobClient = new storage_blob_1.BlockBlobClient(settings.uploadUrl);
const [response1, response2] = await Promise.all([
req(`/batch/${uuid}/report/${uuid}`, {
method: 'POST',
}),
blobClient.upload(JSON.stringify(data), JSON.stringify(data).length),
]);
return {
respCreateUpload: await response1.json(),
respUpload: JSON.stringify(response2),
};
}
async function download(uuid) {
const response = await req(`/batch/${uuid}/report/${uuid}`, {
method: 'GET',
});
return response.json();
}
};
exports.InternalData = {
async write({ testInfo, data, eyes, uuid }) {
var _a, _b;
const logger = ((_a = eyes.getLogger) === null || _a === void 0 ? void 0 : _a.call(eyes)) || (0, logger_1.makeLogger)();
const uploadUrl = eyes._eyes.test.account.batchExecReportsUrl
.replace('__batch_id__', uuid)
.replace('__report_id__', uuid);
const { upload } = makeReporterRequest({
serverUrl: (_b = eyes.getServerUrl()) !== null && _b !== void 0 ? _b : 'https://eyes.applitools.com',
apiKey: eyes.getApiKey(),
uploadUrl,
logger: logger,
});
const response = await upload(uuid, {
key: `${testInfo.testId}--${testInfo.retry}`,
data,
});
logger.log('Data written successfully:', response);
},
async consume(uuid, eyes) {
var _a;
const logger = ((_a = eyes.getLogger) === null || _a === void 0 ? void 0 : _a.call(eyes)) || (0, logger_1.makeLogger)();
const { download } = makeReporterRequest({
serverUrl: eyes.getServerUrl(),
apiKey: eyes.getApiKey(),
logger: logger,
});
for (let retry = 0; retry < 3; retry++) {
try {
const response = await download(uuid);
if (utils.types.has(response, 'status') && response.status !== 200) {
throw new Error(`Failed to consume data for UUID ${uuid}: ${response.status} - ${JSON.stringify(response)}`);
}
return { key: response.key, data: response.data };
}
catch (error) {
logger.warn('Failed to consume data:', error, ". Perhaps the test doesn't include Applitools tests?");
}
await utils.general.sleep(1000 * (retry + 1)); // the backend might need a few seconds to process the upload
}
},
};