omni-dashboard-playwright-reporter
Version:
Playwright client for publishing test results to Omni Dashboard - Transform Your Test Automation with AI-Powered Insights
312 lines (311 loc) • 14.1 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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.OmniPlaywrightClient = void 0;
const test_1 = require("@playwright/test");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
class OmniPlaywrightClient {
constructor(config) {
this.config = config;
}
async getContext() {
if (!this.context) {
this.context = await test_1.request.newContext({
baseURL: this.config.baseUrl,
extraHTTPHeaders: {
'x-api-key': this.config.apiKey,
'Content-Type': 'application/json'
}
});
}
return this.context;
}
async createBuild(requestBody) {
const url = `${this.config.baseUrl}/projects/${this.config.projectId}/builds`;
// Reduced logging for performance
if (process.env.OMNI_DEBUG === 'true') {
console.log("Create Build URL:", url);
console.log("Create Build Request Payload: ", JSON.stringify(requestBody, null, 2));
}
try {
const context = await this.getContext();
const response = await context.post(url, {
data: requestBody
});
const body = await response.json();
// Reduced logging for performance
if (process.env.OMNI_DEBUG === 'true') {
console.log("Create Build Response (%s) %s:", response.status(), JSON.stringify(body, null, 2));
}
// Check if the response is successful
if (!response.ok()) {
const errorMessage = body?.error?.message || `API returned status ${response.status()}`;
throw new Error(`Failed to create build: ${errorMessage}`);
}
const createBuildResponse = body;
// Validate response structure
if (!createBuildResponse.success || !createBuildResponse.data) {
throw new Error(`Invalid response structure: ${JSON.stringify(body)}`);
}
return createBuildResponse;
}
catch (error) {
console.error("Error creating build:", error.message);
throw error;
}
}
async completeBuild(requestBody) {
const url = `${this.config.baseUrl}/projects/${this.config.projectId}/builds?build_id=${requestBody.build_id}`;
// Reduced logging for performance
if (process.env.OMNI_DEBUG === 'true') {
console.log("Complete Build URL:", url);
console.log("Complete Build Request Payload: ", JSON.stringify(requestBody, null, 2));
}
try {
const context = await this.getContext();
const response = await context.patch(url, {
data: requestBody
});
const body = await response.json();
// Reduced logging for performance
if (process.env.OMNI_DEBUG === 'true') {
console.log("Complete Build Response (%s) %s:", response.status(), JSON.stringify(body, null, 2));
}
// Check if the response is successful
if (!response.ok()) {
const errorMessage = body?.error?.message || `API returned status ${response.status()}`;
throw new Error(`Failed to complete build: ${errorMessage}`);
}
const completeBuildResponse = body;
// Validate response structure
if (!completeBuildResponse.success || !completeBuildResponse.data) {
throw new Error(`Invalid response structure: ${JSON.stringify(body)}`);
}
return completeBuildResponse;
}
catch (error) {
console.error("Error completing build:", error.message);
throw error;
}
}
async uploadTestCase(test, result, buildId) {
const url = `${this.config.baseUrl}/projects/${this.config.projectId}/test-cases`;
// Reduced logging for performance - only log URL, not full payload
if (process.env.OMNI_DEBUG === 'true') {
console.log("Upload TestCase URL:", url);
}
const payload = this.createTestCasePayload(test, result, buildId);
// Skip expensive JSON.stringify in production - only log in debug mode
if (process.env.OMNI_DEBUG === 'true') {
console.log("Upload TestCase Request Payload: ", JSON.stringify(payload, null, 2));
}
try {
const context = await this.getContext();
const response = await context.post(url, {
data: payload
});
const body = await response.json();
// Reduced logging for performance
if (process.env.OMNI_DEBUG === 'true') {
console.log("Upload TestCase Response (%s) %s:", response.status(), JSON.stringify(body, null, 2));
}
// Check if the response is successful
if (!response.ok()) {
const errorMessage = body?.error?.message || `API returned status ${response.status()}`;
throw new Error(`Failed to upload test case: ${errorMessage}`);
}
const uploadTestCaseResponse = body;
// Validate response structure
if (!uploadTestCaseResponse.success || !uploadTestCaseResponse.data) {
throw new Error(`Invalid response structure: ${JSON.stringify(body)}`);
}
return uploadTestCaseResponse;
}
catch (error) {
console.error("In Upload TestCase. Error uploading test case:", error.message);
throw error;
}
}
mapStatus(status) {
if (status === 'timedOut' || status === 'interrupted' || status === 'failed')
return 'failed';
if (status === 'skipped')
return 'skipped';
return 'passed';
}
createTestCasePayload(test, result, buildId) {
const title = test.title;
const test_status = this.mapStatus(result.status);
// Create steps from test steps
const test_steps = result.steps.map((step, index) => ({
name: step.title,
sequence_number: index + 1,
duration: Math.max(0, step.duration || 0), // Ensure duration is never negative
status: step.error ? 'failed' : 'passed',
error_message: step.error?.message,
stack_trace: step.error?.stack
}));
// Create stdout entries
const stdout = [{
timestamp: new Date().toISOString(),
level: test_status === 'passed' ? 'INFO' : 'ERROR',
message: `${title} ${test_status}`
}];
// Create screenshots entries
const screenshots = result.attachments
.filter((att) => att.contentType === 'image/png' && att.path)
.map((att) => ({
name: att.path,
timestamp: new Date().toISOString()
}));
// Create traces entries
const traces = result.attachments
.filter((att) => att.name === 'trace' && att.contentType === 'application/zip')
.map((att) => ({
name: att.path
}));
// Create annotations from test annotations
const annotations = result.annotations.map((ann) => ({
type: ann.type,
description: ann.description
}));
const singleTestCase = {
name: title,
module: annotations.find((ann) => ann.type === 'module')?.description || 'Others',
priority: annotations.find((ann) => ann.type === 'priority')?.description,
tags: test.tags?.map((tag) => tag.replace(/^@/, '')) || [],
status: test_status,
duration: Math.max(0, result.duration || 0), // Ensure duration is never negative
test_steps,
stdout,
screenshots,
traces,
annotations,
error_message: result.error?.message,
error_stack_trace: result.error?.stack
};
return {
build_id: buildId,
test_cases: [singleTestCase]
};
}
async uploadScreenshots(test_case) {
if (test_case.screenshots_s3_paths?.length > 0) {
// Reduced logging for performance
if (process.env.OMNI_DEBUG === 'true') {
console.log("Screenshots to be uploaded:", test_case.screenshots_s3_paths.length);
}
// Use Promise.allSettled to continue even if some uploads fail
await Promise.allSettled(test_case.screenshots_s3_paths.map(async (screenshot) => {
if (process.env.OMNI_DEBUG === 'true') {
console.log("Uploading Screenshot:", screenshot.local_path);
}
try {
const filePath = path.resolve(screenshot.local_path);
const fileData = await fs.promises.readFile(filePath);
if (process.env.OMNI_DEBUG === 'true') {
console.log("Upload URL:", screenshot.upload_url);
}
const response = await fetch(screenshot.upload_url, {
method: 'PUT',
headers: {
'Content-Type': 'image/png',
'Content-Length': fileData.length.toString()
},
body: fileData
});
if (process.env.OMNI_DEBUG === 'true') {
console.log('Uploaded screenshot:', screenshot.name, 'Status:', response.status);
}
if (!response.ok) {
const errorText = await response.text();
console.error('S3 upload failed:', errorText);
throw new Error(`S3 upload failed with status ${response.status}: ${errorText}`);
}
}
catch (uploadErr) {
// Log error but don't throw - allow other screenshots to upload
console.error('Failed to upload screenshot:', screenshot.name, uploadErr?.message || uploadErr);
// Don't re-throw to prevent blocking other uploads
}
}));
}
}
async uploadTraces(test_case) {
if (test_case.traces_s3_paths?.length > 0) {
// Reduced logging for performance
if (process.env.OMNI_DEBUG === 'true') {
console.log("Traces to be uploaded:", test_case.traces_s3_paths.length);
}
// Use Promise.allSettled to continue even if some uploads fail
await Promise.allSettled(test_case.traces_s3_paths.map(async (trace) => {
if (process.env.OMNI_DEBUG === 'true') {
console.log("Uploading Trace:", trace.name);
console.log("Uploading Trace Path:", trace.local_path);
}
try {
const filePath = path.resolve(trace.local_path);
const fileData = await fs.promises.readFile(filePath);
if (process.env.OMNI_DEBUG === 'true') {
console.log("Upload URL:", trace.upload_url);
}
const response = await fetch(trace.upload_url, {
method: 'PUT',
headers: {
'Content-Type': 'application/zip',
'Content-Length': fileData.length.toString()
},
body: fileData
});
if (process.env.OMNI_DEBUG === 'true') {
console.log('Uploaded trace:', trace.name, 'Status:', response.status);
}
if (!response.ok) {
const errorText = await response.text();
console.error('S3 upload failed:', errorText);
throw new Error(`S3 upload failed with status ${response.status}: ${errorText}`);
}
}
catch (uploadErr) {
// Log error but don't throw - allow other traces to upload
console.error('Failed to upload trace:', trace.name, uploadErr?.message || uploadErr);
// Don't re-throw to prevent blocking other uploads
}
}));
}
}
}
exports.OmniPlaywrightClient = OmniPlaywrightClient;