@azure/playwright 1.0.1-alpha.20251224.1 → 1.0.1-alpha.20251225.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/dist/browser/common/constants.d.ts +13 -0
- package/dist/browser/common/constants.d.ts.map +1 -1
- package/dist/browser/common/constants.js +13 -0
- package/dist/browser/common/constants.js.map +1 -1
- package/dist/browser/common/messages.d.ts +41 -0
- package/dist/browser/common/messages.d.ts.map +1 -1
- package/dist/browser/common/messages.js +41 -0
- package/dist/browser/common/messages.js.map +1 -1
- package/dist/browser/common/playwrightServiceConfig.d.ts +2 -0
- package/dist/browser/common/playwrightServiceConfig.d.ts.map +1 -1
- package/dist/browser/common/playwrightServiceConfig.js +4 -0
- package/dist/browser/common/playwrightServiceConfig.js.map +1 -1
- package/dist/browser/common/types.d.ts +13 -0
- package/dist/browser/common/types.d.ts.map +1 -1
- package/dist/browser/common/types.js.map +1 -1
- package/dist/browser/reporter/index.d.ts +10 -0
- package/dist/browser/reporter/index.d.ts.map +1 -0
- package/dist/browser/reporter/index.js +12 -0
- package/dist/browser/reporter/index.js.map +1 -0
- package/dist/browser/reporter/playwrightReporter.d.ts +21 -0
- package/dist/browser/reporter/playwrightReporter.d.ts.map +1 -0
- package/dist/browser/reporter/playwrightReporter.js +88 -0
- package/dist/browser/reporter/playwrightReporter.js.map +1 -0
- package/dist/browser/utils/playwrightReporterStorageManager.d.ts +14 -0
- package/dist/browser/utils/playwrightReporterStorageManager.d.ts.map +1 -0
- package/dist/browser/utils/playwrightReporterStorageManager.js +310 -0
- package/dist/browser/utils/playwrightReporterStorageManager.js.map +1 -0
- package/dist/browser/utils/playwrightServiceApicall.d.ts +1 -0
- package/dist/browser/utils/playwrightServiceApicall.d.ts.map +1 -1
- package/dist/browser/utils/playwrightServiceApicall.js +18 -1
- package/dist/browser/utils/playwrightServiceApicall.js.map +1 -1
- package/dist/browser/utils/utils.d.ts +14 -1
- package/dist/browser/utils/utils.d.ts.map +1 -1
- package/dist/browser/utils/utils.js +119 -0
- package/dist/browser/utils/utils.js.map +1 -1
- package/dist/commonjs/common/constants.d.ts +13 -0
- package/dist/commonjs/common/constants.d.ts.map +1 -1
- package/dist/commonjs/common/constants.js +14 -1
- package/dist/commonjs/common/constants.js.map +1 -1
- package/dist/commonjs/common/messages.d.ts +41 -0
- package/dist/commonjs/common/messages.d.ts.map +1 -1
- package/dist/commonjs/common/messages.js +41 -0
- package/dist/commonjs/common/messages.js.map +1 -1
- package/dist/commonjs/common/playwrightServiceConfig.d.ts +2 -0
- package/dist/commonjs/common/playwrightServiceConfig.d.ts.map +1 -1
- package/dist/commonjs/common/playwrightServiceConfig.js +4 -0
- package/dist/commonjs/common/playwrightServiceConfig.js.map +1 -1
- package/dist/commonjs/common/types.d.ts +13 -0
- package/dist/commonjs/common/types.d.ts.map +1 -1
- package/dist/commonjs/common/types.js.map +1 -1
- package/dist/commonjs/reporter/index.d.ts +10 -0
- package/dist/commonjs/reporter/index.d.ts.map +1 -0
- package/dist/commonjs/reporter/index.js +15 -0
- package/dist/commonjs/reporter/index.js.map +1 -0
- package/dist/commonjs/reporter/playwrightReporter.d.ts +21 -0
- package/dist/commonjs/reporter/playwrightReporter.d.ts.map +1 -0
- package/dist/commonjs/reporter/playwrightReporter.js +91 -0
- package/dist/commonjs/reporter/playwrightReporter.js.map +1 -0
- package/dist/commonjs/utils/playwrightReporterStorageManager.d.ts +14 -0
- package/dist/commonjs/utils/playwrightReporterStorageManager.d.ts.map +1 -0
- package/dist/commonjs/utils/playwrightReporterStorageManager.js +314 -0
- package/dist/commonjs/utils/playwrightReporterStorageManager.js.map +1 -0
- package/dist/commonjs/utils/playwrightServiceApicall.d.ts +1 -0
- package/dist/commonjs/utils/playwrightServiceApicall.d.ts.map +1 -1
- package/dist/commonjs/utils/playwrightServiceApicall.js +17 -0
- package/dist/commonjs/utils/playwrightServiceApicall.js.map +1 -1
- package/dist/commonjs/utils/utils.d.ts +14 -1
- package/dist/commonjs/utils/utils.d.ts.map +1 -1
- package/dist/commonjs/utils/utils.js +125 -0
- package/dist/commonjs/utils/utils.js.map +1 -1
- package/dist/esm/common/constants.d.ts +13 -0
- package/dist/esm/common/constants.d.ts.map +1 -1
- package/dist/esm/common/constants.js +13 -0
- package/dist/esm/common/constants.js.map +1 -1
- package/dist/esm/common/messages.d.ts +41 -0
- package/dist/esm/common/messages.d.ts.map +1 -1
- package/dist/esm/common/messages.js +41 -0
- package/dist/esm/common/messages.js.map +1 -1
- package/dist/esm/common/playwrightServiceConfig.d.ts +2 -0
- package/dist/esm/common/playwrightServiceConfig.d.ts.map +1 -1
- package/dist/esm/common/playwrightServiceConfig.js +4 -0
- package/dist/esm/common/playwrightServiceConfig.js.map +1 -1
- package/dist/esm/common/types.d.ts +13 -0
- package/dist/esm/common/types.d.ts.map +1 -1
- package/dist/esm/common/types.js.map +1 -1
- package/dist/esm/reporter/index.d.ts +10 -0
- package/dist/esm/reporter/index.d.ts.map +1 -0
- package/dist/esm/reporter/index.js +12 -0
- package/dist/esm/reporter/index.js.map +1 -0
- package/dist/esm/reporter/playwrightReporter.d.ts +21 -0
- package/dist/esm/reporter/playwrightReporter.d.ts.map +1 -0
- package/dist/esm/reporter/playwrightReporter.js +88 -0
- package/dist/esm/reporter/playwrightReporter.js.map +1 -0
- package/dist/esm/utils/playwrightReporterStorageManager.d.ts +14 -0
- package/dist/esm/utils/playwrightReporterStorageManager.d.ts.map +1 -0
- package/dist/esm/utils/playwrightReporterStorageManager.js +310 -0
- package/dist/esm/utils/playwrightReporterStorageManager.js.map +1 -0
- package/dist/esm/utils/playwrightServiceApicall.d.ts +1 -0
- package/dist/esm/utils/playwrightServiceApicall.d.ts.map +1 -1
- package/dist/esm/utils/playwrightServiceApicall.js +18 -1
- package/dist/esm/utils/playwrightServiceApicall.js.map +1 -1
- package/dist/esm/utils/utils.d.ts +14 -1
- package/dist/esm/utils/utils.d.ts.map +1 -1
- package/dist/esm/utils/utils.js +119 -0
- package/dist/esm/utils/utils.js.map +1 -1
- package/dist/react-native/common/constants.d.ts +13 -0
- package/dist/react-native/common/constants.d.ts.map +1 -1
- package/dist/react-native/common/constants.js +13 -0
- package/dist/react-native/common/constants.js.map +1 -1
- package/dist/react-native/common/messages.d.ts +41 -0
- package/dist/react-native/common/messages.d.ts.map +1 -1
- package/dist/react-native/common/messages.js +41 -0
- package/dist/react-native/common/messages.js.map +1 -1
- package/dist/react-native/common/playwrightServiceConfig.d.ts +2 -0
- package/dist/react-native/common/playwrightServiceConfig.d.ts.map +1 -1
- package/dist/react-native/common/playwrightServiceConfig.js +4 -0
- package/dist/react-native/common/playwrightServiceConfig.js.map +1 -1
- package/dist/react-native/common/types.d.ts +13 -0
- package/dist/react-native/common/types.d.ts.map +1 -1
- package/dist/react-native/common/types.js.map +1 -1
- package/dist/react-native/reporter/index.d.ts +10 -0
- package/dist/react-native/reporter/index.d.ts.map +1 -0
- package/dist/react-native/reporter/index.js +12 -0
- package/dist/react-native/reporter/index.js.map +1 -0
- package/dist/react-native/reporter/playwrightReporter.d.ts +21 -0
- package/dist/react-native/reporter/playwrightReporter.d.ts.map +1 -0
- package/dist/react-native/reporter/playwrightReporter.js +88 -0
- package/dist/react-native/reporter/playwrightReporter.js.map +1 -0
- package/dist/react-native/utils/playwrightReporterStorageManager.d.ts +14 -0
- package/dist/react-native/utils/playwrightReporterStorageManager.d.ts.map +1 -0
- package/dist/react-native/utils/playwrightReporterStorageManager.js +310 -0
- package/dist/react-native/utils/playwrightReporterStorageManager.js.map +1 -0
- package/dist/react-native/utils/playwrightServiceApicall.d.ts +1 -0
- package/dist/react-native/utils/playwrightServiceApicall.d.ts.map +1 -1
- package/dist/react-native/utils/playwrightServiceApicall.js +18 -1
- package/dist/react-native/utils/playwrightServiceApicall.js.map +1 -1
- package/dist/react-native/utils/utils.d.ts +14 -1
- package/dist/react-native/utils/utils.d.ts.map +1 -1
- package/dist/react-native/utils/utils.js +119 -0
- package/dist/react-native/utils/utils.js.map +1 -1
- package/package.json +22 -2
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { BlobServiceClient } from "@azure/storage-blob";
|
|
4
|
+
import { coreLogger } from "../common/logger.js";
|
|
5
|
+
import { readFileSync, existsSync, writeFileSync, createReadStream } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { UploadConstants } from "../common/constants.js";
|
|
8
|
+
import { ServiceErrorMessageConstants } from "../common/messages.js";
|
|
9
|
+
import { populateValuesFromServiceUrl, calculateOptimalConcurrency, collectAllFiles, } from "./utils.js";
|
|
10
|
+
import { PlaywrightServiceConfig } from "../common/playwrightServiceConfig.js";
|
|
11
|
+
export class PlaywrightReporterStorageManager {
|
|
12
|
+
async uploadHtmlReportFolder(credential, runId, outputFolder, workspaceDetails) {
|
|
13
|
+
coreLogger.info(`Starting HTML report upload for runId: ${runId}, outputFolder: ${outputFolder}`);
|
|
14
|
+
try {
|
|
15
|
+
coreLogger.info(`Received workspace details: ${JSON.stringify(workspaceDetails, null, 2)}`);
|
|
16
|
+
if (!workspaceDetails.storageUri) {
|
|
17
|
+
throw new Error(ServiceErrorMessageConstants.STORAGE_URI_NOT_FOUND.message);
|
|
18
|
+
}
|
|
19
|
+
coreLogger.info(`Extracting storage account from URI: ${workspaceDetails.storageUri}`);
|
|
20
|
+
const blobServiceClient = new BlobServiceClient(workspaceDetails?.storageUri, credential);
|
|
21
|
+
const serviceUrlInfo = populateValuesFromServiceUrl();
|
|
22
|
+
if (!serviceUrlInfo?.accountId) {
|
|
23
|
+
throw new Error(ServiceErrorMessageConstants.UNABLE_TO_EXTRACT_WORKSPACE_ID.message);
|
|
24
|
+
}
|
|
25
|
+
const containerName = serviceUrlInfo.accountId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
26
|
+
const containerClient = blobServiceClient.getContainerClient(containerName);
|
|
27
|
+
const containerExists = await containerClient.exists();
|
|
28
|
+
if (!containerExists) {
|
|
29
|
+
await containerClient.create();
|
|
30
|
+
console.log(`Created new container for this workspace: ${containerName}`);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.log(`Using existing container for this workspace: ${containerName}`);
|
|
34
|
+
}
|
|
35
|
+
const folderName = runId;
|
|
36
|
+
console.log(`Folder created for this run: ${folderName}`);
|
|
37
|
+
await this.modifyIndexHtml(outputFolder);
|
|
38
|
+
await this.uploadSasWorkerFile(containerClient, folderName);
|
|
39
|
+
await this.uploadFolderInParallel(containerClient, outputFolder, outputFolder, folderName);
|
|
40
|
+
console.log(`✅ Successfully uploaded Playwright report for run: ${runId} to Azure Storage.`);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
44
|
+
coreLogger.error(`Failed to upload HTML report: ${errorMessage}`);
|
|
45
|
+
throw new Error(ServiceErrorMessageConstants.HTML_REPORT_UPLOAD_FAILED.formatWithError(errorMessage));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async uploadSasWorkerFile(containerClient, folderName) {
|
|
49
|
+
coreLogger.info(`Starting sasworker.js upload for folder: ${folderName}`);
|
|
50
|
+
try {
|
|
51
|
+
const sasWorkerPath = join(process.cwd(), "sasworker.js");
|
|
52
|
+
coreLogger.info(`Resolving sasworker.js path: ${sasWorkerPath}`);
|
|
53
|
+
if (!existsSync(sasWorkerPath)) {
|
|
54
|
+
coreLogger.error(`sasworker.js file not found at path: ${sasWorkerPath}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
coreLogger.info(`Found sasworker.js file at: ${sasWorkerPath}`);
|
|
58
|
+
const fileContent = readFileSync(sasWorkerPath);
|
|
59
|
+
const blobName = `${folderName}/sasworker.js`;
|
|
60
|
+
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
|
|
61
|
+
await blockBlobClient.upload(fileContent, fileContent.length, {
|
|
62
|
+
blobHTTPHeaders: {
|
|
63
|
+
blobContentType: "application/javascript",
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
coreLogger.info("✅ Uploaded service worker file as sasworker.js");
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
coreLogger.error(`Error uploading service worker file: ${error instanceof Error ? error.message : String(error)}`);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async modifyIndexHtml(outputFolder) {
|
|
74
|
+
coreLogger.info(`Starting HTML modification for folder: ${outputFolder}`);
|
|
75
|
+
const indexPath = join(outputFolder, "index.html");
|
|
76
|
+
if (!existsSync(indexPath)) {
|
|
77
|
+
coreLogger.error(`index.html not found at path: ${indexPath}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
coreLogger.info(`Found index.html at: ${indexPath}`);
|
|
81
|
+
try {
|
|
82
|
+
let htmlContent = readFileSync(indexPath, "utf-8");
|
|
83
|
+
// Service worker registration script
|
|
84
|
+
const serviceWorkerScript = `
|
|
85
|
+
<script>
|
|
86
|
+
(async () => {
|
|
87
|
+
if (!('serviceWorker' in navigator)) return;
|
|
88
|
+
|
|
89
|
+
const sas = window.location.search;
|
|
90
|
+
const swUrl = './sasworker.js' + sas;
|
|
91
|
+
const scope = './';
|
|
92
|
+
await navigator.serviceWorker.register(swUrl, { scope });
|
|
93
|
+
await navigator.serviceWorker.ready;
|
|
94
|
+
if (!navigator.serviceWorker.controller && !sessionStorage.getItem('__sw_bootstrap__')) {
|
|
95
|
+
sessionStorage.setItem('__sw_bootstrap__', '1');
|
|
96
|
+
location.reload();
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
</script>`;
|
|
100
|
+
const titleMatch = htmlContent.match(/<\/title>/i);
|
|
101
|
+
if (titleMatch) {
|
|
102
|
+
coreLogger.info(`Found title tag, injecting service worker script at position: ${titleMatch.index + titleMatch[0].length}`);
|
|
103
|
+
const insertPosition = titleMatch.index + titleMatch[0].length;
|
|
104
|
+
htmlContent =
|
|
105
|
+
htmlContent.slice(0, insertPosition) +
|
|
106
|
+
serviceWorkerScript +
|
|
107
|
+
htmlContent.slice(insertPosition);
|
|
108
|
+
writeFileSync(indexPath, htmlContent, "utf-8");
|
|
109
|
+
coreLogger.info("Successfully wrote modified HTML content to file");
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
coreLogger.error("Title tag not found in index.html - unable to inject service worker script");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
coreLogger.error(`Error modifying index.html: ${error instanceof Error ? error.message : String(error)}`);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Uploads the entire Playwright HTML report folder after tests complete.
|
|
121
|
+
async uploadPlaywrightHtmlReportAfterTests(outputFolderName, workspaceMetadata) {
|
|
122
|
+
coreLogger.info(`Starting post-test HTML report upload, folder name: ${outputFolderName || "default (playwright-report)"}`);
|
|
123
|
+
const cred = PlaywrightServiceConfig.instance.credential;
|
|
124
|
+
if (!cred) {
|
|
125
|
+
coreLogger.error("No credential found for authentication");
|
|
126
|
+
throw new Error(ServiceErrorMessageConstants.NO_CRED_ENTRA_AUTH_ERROR.message);
|
|
127
|
+
}
|
|
128
|
+
coreLogger.info("Credential found for authentication");
|
|
129
|
+
const folderName = outputFolderName || "playwright-report";
|
|
130
|
+
const outputFolderPath = join(process.cwd(), folderName);
|
|
131
|
+
if (!existsSync(outputFolderPath)) {
|
|
132
|
+
coreLogger.error(`HTML report folder not found: ${outputFolderPath}`);
|
|
133
|
+
throw new Error(ServiceErrorMessageConstants.HTML_REPORT_FOLDER_NOT_FOUND.formatWithFolder(folderName));
|
|
134
|
+
}
|
|
135
|
+
coreLogger.info(`HTML report folder found: ${outputFolderPath}`);
|
|
136
|
+
const testRunId = PlaywrightServiceConfig.instance.runId;
|
|
137
|
+
coreLogger.info(`Starting upload for test run ID: ${testRunId}`);
|
|
138
|
+
await this.uploadHtmlReportFolder(cred, testRunId, outputFolderPath, workspaceMetadata);
|
|
139
|
+
coreLogger.info(`Completed upload for test run ID: ${testRunId}`);
|
|
140
|
+
}
|
|
141
|
+
// Parallel Upload Engine - Core upload orchestration with performance optimization
|
|
142
|
+
async uploadFolderInParallel(containerClient, folderPath, basePath, runIdFolderPrefix) {
|
|
143
|
+
// Sort by size descending - upload large files first for better parallelization
|
|
144
|
+
const filesToUpload = collectAllFiles(folderPath, basePath, runIdFolderPrefix).sort((a, b) => b.size - a.size);
|
|
145
|
+
if (filesToUpload.length === 0) {
|
|
146
|
+
return { uploadedFiles: [], totalFiles: 0, totalSize: 0, uploadTime: 0 };
|
|
147
|
+
}
|
|
148
|
+
const totalSize = filesToUpload.reduce((sum, file) => sum + file.size, 0);
|
|
149
|
+
const concurrency = calculateOptimalConcurrency(filesToUpload);
|
|
150
|
+
coreLogger.info(`Calculated optimal concurrency: ${concurrency} for ${filesToUpload.length} files (total size: ${(totalSize / 1024 / 1024).toFixed(2)} MB)`);
|
|
151
|
+
const uploadStartTime = Date.now();
|
|
152
|
+
coreLogger.info(`Starting parallel upload with ${concurrency} concurrent operations`);
|
|
153
|
+
const results = await this.uploadWithConcurrencyControl(containerClient, filesToUpload, concurrency);
|
|
154
|
+
const uploadEndTime = Date.now();
|
|
155
|
+
const uploadTime = uploadEndTime - uploadStartTime;
|
|
156
|
+
coreLogger.info(`Upload completed in ${uploadTime}ms (${(uploadTime / 1000).toFixed(2)}s)`);
|
|
157
|
+
const failed = results.filter((r) => r.status === "rejected").length;
|
|
158
|
+
const successful = results.filter((r) => r.status === "fulfilled").length;
|
|
159
|
+
coreLogger.info(`Upload results: ${successful} successful, ${failed} failed out of ${results.length} total files`);
|
|
160
|
+
if (failed > 0) {
|
|
161
|
+
const errors = results
|
|
162
|
+
.filter((r) => r.status === "rejected")
|
|
163
|
+
.map((r) => r.reason.message)
|
|
164
|
+
.slice(0, 5); // Show first 5 errors
|
|
165
|
+
errors.forEach((error, index) => {
|
|
166
|
+
coreLogger.error(` ${index + 1}. ${error}`);
|
|
167
|
+
});
|
|
168
|
+
throw new Error(ServiceErrorMessageConstants.UPLOAD_FAILED_MULTIPLE_FILES.formatWithDetails(failed, errors));
|
|
169
|
+
}
|
|
170
|
+
const uploadedFiles = results
|
|
171
|
+
.filter((r) => r.status === "fulfilled")
|
|
172
|
+
.map((r) => r.value);
|
|
173
|
+
return {
|
|
174
|
+
uploadedFiles,
|
|
175
|
+
totalFiles: filesToUpload.length,
|
|
176
|
+
totalSize,
|
|
177
|
+
uploadTime,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// Concurrency Control System - Manages parallel execution with controlled resource usage
|
|
181
|
+
async uploadWithConcurrencyControl(containerClient, files, concurrency) {
|
|
182
|
+
const uploadTasks = files.map((fileInfo) => async () => {
|
|
183
|
+
try {
|
|
184
|
+
await this.uploadSingleFileOptimized(containerClient, fileInfo);
|
|
185
|
+
return fileInfo.relativePath;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
throw new Error(`${fileInfo.relativePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
return this.executeWithOptimizedBatching(uploadTasks, concurrency);
|
|
192
|
+
}
|
|
193
|
+
// Optimized Batch Execution Engine - High-performance task processing system
|
|
194
|
+
async executeWithOptimizedBatching(tasks, concurrency) {
|
|
195
|
+
const results = new Array(tasks.length);
|
|
196
|
+
let completedTasks = 0;
|
|
197
|
+
// Splits tasks into optimal batches to balance memory usage vs. throughput
|
|
198
|
+
const batchSize = Math.min(UploadConstants.BATCH_SIZE, concurrency * 2);
|
|
199
|
+
const batches = [];
|
|
200
|
+
// Each batch will be processed with full concurrency before moving to next batch
|
|
201
|
+
for (let i = 0; i < tasks.length; i += batchSize) {
|
|
202
|
+
batches.push(tasks.slice(i, i + batchSize));
|
|
203
|
+
}
|
|
204
|
+
// Process each batch sequentially to maintain memory efficiency
|
|
205
|
+
coreLogger.info(`Processing ${batches.length} batches with ${batchSize} tasks per batch`);
|
|
206
|
+
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
|
|
207
|
+
const batch = batches[batchIndex];
|
|
208
|
+
const batchStartIndex = batchIndex * batchSize;
|
|
209
|
+
coreLogger.info(`Starting batch ${batchIndex + 1}/${batches.length} with ${batch.length} tasks`);
|
|
210
|
+
// Transform each task into a promise that captures results at correct index
|
|
211
|
+
const batchPromises = batch.map(async (task, taskIndex) => {
|
|
212
|
+
const globalIndex = batchStartIndex + taskIndex;
|
|
213
|
+
try {
|
|
214
|
+
const result = await task();
|
|
215
|
+
results[globalIndex] = { status: "fulfilled", value: result };
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
results[globalIndex] = { status: "rejected", reason: error };
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
const executing = [];
|
|
224
|
+
for (const promise of batchPromises) {
|
|
225
|
+
if (executing.length >= concurrency) {
|
|
226
|
+
await Promise.race(executing);
|
|
227
|
+
}
|
|
228
|
+
const wrappedPromise = promise
|
|
229
|
+
.then((result) => {
|
|
230
|
+
completedTasks++;
|
|
231
|
+
return result;
|
|
232
|
+
})
|
|
233
|
+
.catch(() => {
|
|
234
|
+
completedTasks++;
|
|
235
|
+
})
|
|
236
|
+
.finally(() => {
|
|
237
|
+
// Cleanup: remove completed promise from executing array
|
|
238
|
+
const index = executing.indexOf(wrappedPromise);
|
|
239
|
+
if (index > -1)
|
|
240
|
+
executing.splice(index, 1);
|
|
241
|
+
});
|
|
242
|
+
executing.push(wrappedPromise);
|
|
243
|
+
}
|
|
244
|
+
// Wait for all promises in current batch to complete before moving to next batch
|
|
245
|
+
await Promise.allSettled(executing);
|
|
246
|
+
coreLogger.info(`Completed batch ${batchIndex + 1}/${batches.length}`);
|
|
247
|
+
}
|
|
248
|
+
return results;
|
|
249
|
+
}
|
|
250
|
+
// Individual File Upload with Retry Logic
|
|
251
|
+
async uploadSingleFileOptimized(containerClient, fileInfo) {
|
|
252
|
+
coreLogger.info(`Uploading file: ${fileInfo.relativePath} (${(fileInfo.size / 1024).toFixed(2)} KB, ${fileInfo.contentType})`);
|
|
253
|
+
const blockBlobClient = containerClient.getBlockBlobClient(fileInfo.relativePath);
|
|
254
|
+
const maxRetries = UploadConstants.MAX_RETRY_ATTEMPTS;
|
|
255
|
+
const baseDelay = UploadConstants.RETRY_BASE_DELAY;
|
|
256
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
257
|
+
try {
|
|
258
|
+
await this.performOptimizedUpload(blockBlobClient, fileInfo);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
const isLastAttempt = attempt === maxRetries;
|
|
263
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
264
|
+
coreLogger.info(`Upload attempt ${attempt} failed for ${fileInfo.relativePath}: ${errorMessage}`);
|
|
265
|
+
if (isLastAttempt) {
|
|
266
|
+
coreLogger.error(`All retry attempts exhausted for ${fileInfo.relativePath}`);
|
|
267
|
+
throw new Error(ServiceErrorMessageConstants.UPLOAD_RETRY_EXHAUSTED.formatWithDetails(maxRetries, errorMessage));
|
|
268
|
+
}
|
|
269
|
+
// Exponential backoff with jitter (Azure SDK pattern)
|
|
270
|
+
const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 500;
|
|
271
|
+
coreLogger.info(`Retrying upload for ${fileInfo.relativePath} in ${delay.toFixed(0)}ms (attempt ${attempt + 1}/${maxRetries})`);
|
|
272
|
+
await new Promise((_resolve) => setTimeout(_resolve, delay));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Multi-Strategy Upload Engine - Optimized upload based on file characteristics
|
|
277
|
+
async performOptimizedUpload(blockBlobClient, fileInfo) {
|
|
278
|
+
if (fileInfo.size <= UploadConstants.SMALL_FILE_THRESHOLD) {
|
|
279
|
+
// DIRECT UPLOAD: Optimal for small files (≤1MB)
|
|
280
|
+
const fileContent = readFileSync(fileInfo.fullPath);
|
|
281
|
+
await blockBlobClient.upload(fileContent, fileContent.length, {
|
|
282
|
+
blobHTTPHeaders: {
|
|
283
|
+
blobContentType: fileInfo.contentType,
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
else if (fileInfo.size <= UploadConstants.LARGE_FILE_THRESHOLD) {
|
|
288
|
+
// BLOCK UPLOAD: Optimal for medium files (1MB - 100MB)
|
|
289
|
+
const fileContent = readFileSync(fileInfo.fullPath);
|
|
290
|
+
await blockBlobClient.uploadData(fileContent, {
|
|
291
|
+
blobHTTPHeaders: {
|
|
292
|
+
blobContentType: fileInfo.contentType,
|
|
293
|
+
},
|
|
294
|
+
blockSize: UploadConstants.OPTIMIZED_BLOCK_SIZE,
|
|
295
|
+
concurrency: UploadConstants.PER_FILE_CONCURRENCY,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// STREAMING UPLOAD: Optimal for large files (>100MB)
|
|
300
|
+
const stream = createReadStream(fileInfo.fullPath);
|
|
301
|
+
await blockBlobClient.uploadStream(stream, UploadConstants.STREAM_BUFFER_SIZE, UploadConstants.LARGE_FILE_CONCURRENCY, {
|
|
302
|
+
blobHTTPHeaders: {
|
|
303
|
+
blobContentType: fileInfo.contentType,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
coreLogger.info(`Successfully uploaded: ${fileInfo.relativePath}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=playwrightReporterStorageManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwrightReporterStorageManager.js","sourceRoot":"","sources":["../../../src/utils/playwrightReporterStorageManager.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport { BlobServiceClient } from \"@azure/storage-blob\";\nimport type { TokenCredential } from \"@azure/core-auth\";\nimport { coreLogger } from \"../common/logger.js\";\nimport { readFileSync, existsSync, writeFileSync, createReadStream } from \"fs\";\nimport { join } from \"path\";\nimport { UploadConstants } from \"../common/constants.js\";\nimport { ServiceErrorMessageConstants } from \"../common/messages.js\";\nimport {\n populateValuesFromServiceUrl,\n calculateOptimalConcurrency,\n collectAllFiles,\n} from \"./utils.js\";\nimport { PlaywrightServiceConfig } from \"../common/playwrightServiceConfig.js\";\nimport type { WorkspaceMetaData } from \"../common/types.js\";\n\nexport class PlaywrightReporterStorageManager {\n async uploadHtmlReportFolder(\n credential: TokenCredential,\n runId: string,\n outputFolder: string,\n workspaceDetails: WorkspaceMetaData,\n ): Promise<void> {\n coreLogger.info(\n `Starting HTML report upload for runId: ${runId}, outputFolder: ${outputFolder}`,\n );\n try {\n coreLogger.info(`Received workspace details: ${JSON.stringify(workspaceDetails, null, 2)}`);\n\n if (!workspaceDetails.storageUri) {\n throw new Error(ServiceErrorMessageConstants.STORAGE_URI_NOT_FOUND.message);\n }\n\n coreLogger.info(`Extracting storage account from URI: ${workspaceDetails.storageUri}`);\n const blobServiceClient = new BlobServiceClient(workspaceDetails?.storageUri, credential);\n const serviceUrlInfo = populateValuesFromServiceUrl();\n if (!serviceUrlInfo?.accountId) {\n throw new Error(ServiceErrorMessageConstants.UNABLE_TO_EXTRACT_WORKSPACE_ID.message);\n }\n\n const containerName = serviceUrlInfo.accountId.toLowerCase().replace(/[^a-z0-9-]/g, \"-\");\n const containerClient = blobServiceClient.getContainerClient(containerName);\n\n const containerExists = await containerClient.exists();\n if (!containerExists) {\n await containerClient.create();\n console.log(`Created new container for this workspace: ${containerName}`);\n } else {\n console.log(`Using existing container for this workspace: ${containerName}`);\n }\n\n const folderName = runId;\n console.log(`Folder created for this run: ${folderName}`);\n\n await this.modifyIndexHtml(outputFolder);\n await this.uploadSasWorkerFile(containerClient, folderName);\n await this.uploadFolderInParallel(containerClient, outputFolder, outputFolder, folderName);\n\n console.log(`✅ Successfully uploaded Playwright report for run: ${runId} to Azure Storage.`);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n coreLogger.error(`Failed to upload HTML report: ${errorMessage}`);\n throw new Error(\n ServiceErrorMessageConstants.HTML_REPORT_UPLOAD_FAILED.formatWithError(errorMessage),\n );\n }\n }\n private async uploadSasWorkerFile(containerClient: any, folderName: string): Promise<void> {\n coreLogger.info(`Starting sasworker.js upload for folder: ${folderName}`);\n try {\n const sasWorkerPath = join(process.cwd(), \"sasworker.js\");\n coreLogger.info(`Resolving sasworker.js path: ${sasWorkerPath}`);\n\n if (!existsSync(sasWorkerPath)) {\n coreLogger.error(`sasworker.js file not found at path: ${sasWorkerPath}`);\n return;\n }\n coreLogger.info(`Found sasworker.js file at: ${sasWorkerPath}`);\n const fileContent = readFileSync(sasWorkerPath);\n const blobName = `${folderName}/sasworker.js`;\n\n const blockBlobClient = containerClient.getBlockBlobClient(blobName);\n\n await blockBlobClient.upload(fileContent, fileContent.length, {\n blobHTTPHeaders: {\n blobContentType: \"application/javascript\",\n },\n });\n\n coreLogger.info(\"✅ Uploaded service worker file as sasworker.js\");\n } catch (error) {\n coreLogger.error(\n `Error uploading service worker file: ${error instanceof Error ? error.message : String(error)}`,\n );\n throw error;\n }\n }\n\n private async modifyIndexHtml(outputFolder: string): Promise<void> {\n coreLogger.info(`Starting HTML modification for folder: ${outputFolder}`);\n const indexPath = join(outputFolder, \"index.html\");\n\n if (!existsSync(indexPath)) {\n coreLogger.error(`index.html not found at path: ${indexPath}`);\n return;\n }\n coreLogger.info(`Found index.html at: ${indexPath}`);\n\n try {\n let htmlContent = readFileSync(indexPath, \"utf-8\");\n\n // Service worker registration script\n const serviceWorkerScript = `\n <script>\n (async () => {\n if (!('serviceWorker' in navigator)) return;\n\n const sas = window.location.search;\n const swUrl = './sasworker.js' + sas; \n const scope = './'; \n await navigator.serviceWorker.register(swUrl, { scope });\n await navigator.serviceWorker.ready;\n if (!navigator.serviceWorker.controller && !sessionStorage.getItem('__sw_bootstrap__')) {\n sessionStorage.setItem('__sw_bootstrap__', '1');\n location.reload();\n }\n })();\n </script>`;\n\n const titleMatch = htmlContent.match(/<\\/title>/i);\n if (titleMatch) {\n coreLogger.info(\n `Found title tag, injecting service worker script at position: ${titleMatch.index! + titleMatch[0].length}`,\n );\n const insertPosition = titleMatch.index! + titleMatch[0].length;\n htmlContent =\n htmlContent.slice(0, insertPosition) +\n serviceWorkerScript +\n htmlContent.slice(insertPosition);\n\n writeFileSync(indexPath, htmlContent, \"utf-8\");\n coreLogger.info(\"Successfully wrote modified HTML content to file\");\n } else {\n coreLogger.error(\n \"Title tag not found in index.html - unable to inject service worker script\",\n );\n }\n } catch (error) {\n coreLogger.error(\n `Error modifying index.html: ${error instanceof Error ? error.message : String(error)}`,\n );\n throw error;\n }\n }\n\n // Uploads the entire Playwright HTML report folder after tests complete.\n\n async uploadPlaywrightHtmlReportAfterTests(\n outputFolderName?: string,\n workspaceMetadata?: WorkspaceMetaData | null,\n ): Promise<void> {\n coreLogger.info(\n `Starting post-test HTML report upload, folder name: ${outputFolderName || \"default (playwright-report)\"}`,\n );\n const cred = PlaywrightServiceConfig.instance.credential;\n if (!cred) {\n coreLogger.error(\"No credential found for authentication\");\n throw new Error(ServiceErrorMessageConstants.NO_CRED_ENTRA_AUTH_ERROR.message);\n }\n coreLogger.info(\"Credential found for authentication\");\n\n const folderName = outputFolderName || \"playwright-report\";\n const outputFolderPath = join(process.cwd(), folderName);\n\n if (!existsSync(outputFolderPath)) {\n coreLogger.error(`HTML report folder not found: ${outputFolderPath}`);\n throw new Error(\n ServiceErrorMessageConstants.HTML_REPORT_FOLDER_NOT_FOUND.formatWithFolder(folderName),\n );\n }\n coreLogger.info(`HTML report folder found: ${outputFolderPath}`);\n\n const testRunId = PlaywrightServiceConfig.instance.runId;\n coreLogger.info(`Starting upload for test run ID: ${testRunId}`);\n await this.uploadHtmlReportFolder(cred, testRunId, outputFolderPath, workspaceMetadata!);\n coreLogger.info(`Completed upload for test run ID: ${testRunId}`);\n }\n\n // Parallel Upload Engine - Core upload orchestration with performance optimization\n\n private async uploadFolderInParallel(\n containerClient: any,\n folderPath: string,\n basePath: string,\n runIdFolderPrefix?: string,\n ): Promise<{\n uploadedFiles: string[];\n totalFiles: number;\n totalSize: number;\n uploadTime: number;\n }> {\n // Sort by size descending - upload large files first for better parallelization\n const filesToUpload = collectAllFiles(folderPath, basePath, runIdFolderPrefix).sort(\n (a, b) => b.size - a.size,\n );\n\n if (filesToUpload.length === 0) {\n return { uploadedFiles: [], totalFiles: 0, totalSize: 0, uploadTime: 0 };\n }\n\n const totalSize = filesToUpload.reduce((sum, file) => sum + file.size, 0);\n\n const concurrency = calculateOptimalConcurrency(filesToUpload);\n coreLogger.info(\n `Calculated optimal concurrency: ${concurrency} for ${filesToUpload.length} files (total size: ${(totalSize / 1024 / 1024).toFixed(2)} MB)`,\n );\n const uploadStartTime = Date.now();\n coreLogger.info(`Starting parallel upload with ${concurrency} concurrent operations`);\n const results = await this.uploadWithConcurrencyControl(\n containerClient,\n filesToUpload,\n concurrency,\n );\n\n const uploadEndTime = Date.now();\n const uploadTime = uploadEndTime - uploadStartTime;\n coreLogger.info(`Upload completed in ${uploadTime}ms (${(uploadTime / 1000).toFixed(2)}s)`);\n\n const failed = results.filter((r) => r.status === \"rejected\").length;\n const successful = results.filter((r) => r.status === \"fulfilled\").length;\n coreLogger.info(\n `Upload results: ${successful} successful, ${failed} failed out of ${results.length} total files`,\n );\n\n if (failed > 0) {\n const errors = results\n .filter((r): r is PromiseRejectedResult => r.status === \"rejected\")\n .map((r) => r.reason.message)\n .slice(0, 5); // Show first 5 errors\n\n errors.forEach((error, index) => {\n coreLogger.error(` ${index + 1}. ${error}`);\n });\n\n throw new Error(\n ServiceErrorMessageConstants.UPLOAD_FAILED_MULTIPLE_FILES.formatWithDetails(failed, errors),\n );\n }\n\n const uploadedFiles = results\n .filter((r): r is PromiseFulfilledResult<string> => r.status === \"fulfilled\")\n .map((r) => r.value);\n\n return {\n uploadedFiles,\n totalFiles: filesToUpload.length,\n totalSize,\n uploadTime,\n };\n }\n\n // Concurrency Control System - Manages parallel execution with controlled resource usage\n\n private async uploadWithConcurrencyControl(\n containerClient: any,\n files: Array<{ fullPath: string; relativePath: string; size: number; contentType: string }>,\n concurrency: number,\n ): Promise<PromiseSettledResult<string>[]> {\n const uploadTasks = files.map((fileInfo) => async (): Promise<string> => {\n try {\n await this.uploadSingleFileOptimized(containerClient, fileInfo);\n return fileInfo.relativePath;\n } catch (error) {\n throw new Error(\n `${fileInfo.relativePath}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n });\n\n return this.executeWithOptimizedBatching(uploadTasks, concurrency);\n }\n\n // Optimized Batch Execution Engine - High-performance task processing system\n\n private async executeWithOptimizedBatching<T>(\n tasks: Array<() => Promise<T>>,\n concurrency: number,\n ): Promise<PromiseSettledResult<T>[]> {\n const results: PromiseSettledResult<T>[] = new Array(tasks.length);\n let completedTasks = 0;\n\n // Splits tasks into optimal batches to balance memory usage vs. throughput\n const batchSize = Math.min(UploadConstants.BATCH_SIZE, concurrency * 2);\n const batches: Array<Array<() => Promise<T>>> = [];\n\n // Each batch will be processed with full concurrency before moving to next batch\n for (let i = 0; i < tasks.length; i += batchSize) {\n batches.push(tasks.slice(i, i + batchSize));\n }\n\n // Process each batch sequentially to maintain memory efficiency\n coreLogger.info(`Processing ${batches.length} batches with ${batchSize} tasks per batch`);\n for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {\n const batch = batches[batchIndex];\n const batchStartIndex = batchIndex * batchSize;\n coreLogger.info(\n `Starting batch ${batchIndex + 1}/${batches.length} with ${batch.length} tasks`,\n );\n\n // Transform each task into a promise that captures results at correct index\n const batchPromises = batch.map(async (task, taskIndex) => {\n const globalIndex = batchStartIndex + taskIndex;\n try {\n const result = await task();\n results[globalIndex] = { status: \"fulfilled\", value: result };\n return result;\n } catch (error) {\n results[globalIndex] = { status: \"rejected\", reason: error };\n throw error;\n }\n });\n\n const executing: Promise<any>[] = [];\n\n for (const promise of batchPromises) {\n if (executing.length >= concurrency) {\n await Promise.race(executing);\n }\n const wrappedPromise = promise\n .then((result) => {\n completedTasks++;\n return result;\n })\n .catch(() => {\n completedTasks++;\n })\n .finally(() => {\n // Cleanup: remove completed promise from executing array\n const index = executing.indexOf(wrappedPromise);\n if (index > -1) executing.splice(index, 1);\n });\n\n executing.push(wrappedPromise);\n }\n\n // Wait for all promises in current batch to complete before moving to next batch\n await Promise.allSettled(executing);\n coreLogger.info(`Completed batch ${batchIndex + 1}/${batches.length}`);\n }\n\n return results;\n }\n\n // Individual File Upload with Retry Logic\n\n private async uploadSingleFileOptimized(\n containerClient: any,\n fileInfo: { fullPath: string; relativePath: string; contentType: string; size: number },\n ): Promise<void> {\n coreLogger.info(\n `Uploading file: ${fileInfo.relativePath} (${(fileInfo.size / 1024).toFixed(2)} KB, ${fileInfo.contentType})`,\n );\n const blockBlobClient = containerClient.getBlockBlobClient(fileInfo.relativePath);\n const maxRetries = UploadConstants.MAX_RETRY_ATTEMPTS;\n const baseDelay = UploadConstants.RETRY_BASE_DELAY;\n\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n try {\n await this.performOptimizedUpload(blockBlobClient, fileInfo);\n return;\n } catch (error) {\n const isLastAttempt = attempt === maxRetries;\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n coreLogger.info(\n `Upload attempt ${attempt} failed for ${fileInfo.relativePath}: ${errorMessage}`,\n );\n\n if (isLastAttempt) {\n coreLogger.error(`All retry attempts exhausted for ${fileInfo.relativePath}`);\n throw new Error(\n ServiceErrorMessageConstants.UPLOAD_RETRY_EXHAUSTED.formatWithDetails(\n maxRetries,\n errorMessage,\n ),\n );\n }\n\n // Exponential backoff with jitter (Azure SDK pattern)\n const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 500;\n coreLogger.info(\n `Retrying upload for ${fileInfo.relativePath} in ${delay.toFixed(0)}ms (attempt ${attempt + 1}/${maxRetries})`,\n );\n await new Promise((_resolve) => setTimeout(_resolve, delay));\n }\n }\n }\n\n // Multi-Strategy Upload Engine - Optimized upload based on file characteristics\n\n private async performOptimizedUpload(\n blockBlobClient: any,\n fileInfo: { fullPath: string; relativePath: string; contentType: string; size: number },\n ): Promise<void> {\n if (fileInfo.size <= UploadConstants.SMALL_FILE_THRESHOLD) {\n // DIRECT UPLOAD: Optimal for small files (≤1MB)\n const fileContent = readFileSync(fileInfo.fullPath);\n await blockBlobClient.upload(fileContent, fileContent.length, {\n blobHTTPHeaders: {\n blobContentType: fileInfo.contentType,\n },\n });\n } else if (fileInfo.size <= UploadConstants.LARGE_FILE_THRESHOLD) {\n // BLOCK UPLOAD: Optimal for medium files (1MB - 100MB)\n const fileContent = readFileSync(fileInfo.fullPath);\n await blockBlobClient.uploadData(fileContent, {\n blobHTTPHeaders: {\n blobContentType: fileInfo.contentType,\n },\n blockSize: UploadConstants.OPTIMIZED_BLOCK_SIZE,\n concurrency: UploadConstants.PER_FILE_CONCURRENCY,\n });\n } else {\n // STREAMING UPLOAD: Optimal for large files (>100MB)\n const stream = createReadStream(fileInfo.fullPath);\n await blockBlobClient.uploadStream(\n stream,\n UploadConstants.STREAM_BUFFER_SIZE,\n UploadConstants.LARGE_FILE_CONCURRENCY,\n {\n blobHTTPHeaders: {\n blobContentType: fileInfo.contentType,\n },\n },\n );\n }\n coreLogger.info(`Successfully uploaded: ${fileInfo.relativePath}`);\n }\n}\n"]}
|
|
@@ -11,5 +11,6 @@ export declare class PlaywrightServiceApiCall {
|
|
|
11
11
|
private httpService;
|
|
12
12
|
constructor(httpService?: HttpService);
|
|
13
13
|
patchTestRunAPI(payload: TestRunCreatePayload): Promise<any>;
|
|
14
|
+
getWorkspaceMetadata(): Promise<any>;
|
|
14
15
|
}
|
|
15
16
|
//# sourceMappingURL=playwrightServiceApicall.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwrightServiceApicall.d.ts","sourceRoot":"","sources":["../../../src/utils/playwrightServiceApicall.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"playwrightServiceApicall.d.ts","sourceRoot":"","sources":["../../../src/utils/playwrightServiceApicall.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAI1D;;;;;;GAMG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,WAAW,CAAc;gBAErB,WAAW,CAAC,EAAE,WAAW;IAI/B,eAAe,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,GAAG,CAAC;IA6B5D,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC;CA4B3C"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import { getTestRunApiUrl, getAccessToken, extractErrorMessage, exitWithFailureMessage, } from "./utils.js";
|
|
3
|
+
import { getTestRunApiUrl, getAccessToken, extractErrorMessage, exitWithFailureMessage, getWorkspaceMetaDataApiUrl, } from "./utils.js";
|
|
4
4
|
import { HttpService } from "../common/httpService.js";
|
|
5
5
|
import { ServiceErrorMessageConstants } from "../common/messages.js";
|
|
6
6
|
import { Constants } from "../common/constants.js";
|
|
@@ -36,5 +36,22 @@ export class PlaywrightServiceApiCall {
|
|
|
36
36
|
console.log("Test run created successfully.");
|
|
37
37
|
return response.bodyAsText ? JSON.parse(response.bodyAsText) : {};
|
|
38
38
|
}
|
|
39
|
+
async getWorkspaceMetadata() {
|
|
40
|
+
const baseUrl = getWorkspaceMetaDataApiUrl();
|
|
41
|
+
const token = getAccessToken();
|
|
42
|
+
if (!token) {
|
|
43
|
+
throw new Error("PLAYWRIGHT_SERVICE_ACCESS_TOKEN environment variable is not set.");
|
|
44
|
+
}
|
|
45
|
+
const url = new URL(baseUrl);
|
|
46
|
+
url.searchParams.set("api-version", Constants.LatestAPIVersion);
|
|
47
|
+
const method = "GET";
|
|
48
|
+
const correlationId = crypto.randomUUID();
|
|
49
|
+
const response = await this.httpService.callAPI(method, url.toString(), null, token, "", correlationId);
|
|
50
|
+
if (response.status !== 200) {
|
|
51
|
+
const errorMessage = extractErrorMessage(response?.bodyAsText ?? "");
|
|
52
|
+
exitWithFailureMessage(ServiceErrorMessageConstants.FAILED_TO_GET_WORKSPACE_DETAILS, errorMessage);
|
|
53
|
+
}
|
|
54
|
+
return response.bodyAsText ? JSON.parse(response.bodyAsText) : {};
|
|
55
|
+
}
|
|
39
56
|
}
|
|
40
57
|
//# sourceMappingURL=playwrightServiceApicall.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwrightServiceApicall.js","sourceRoot":"","sources":["../../../src/utils/playwrightServiceApicall.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAClC,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"playwrightServiceApicall.js","sourceRoot":"","sources":["../../../src/utils/playwrightServiceApicall.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\nimport {\n getTestRunApiUrl,\n getAccessToken,\n extractErrorMessage,\n exitWithFailureMessage,\n getWorkspaceMetaDataApiUrl,\n} from \"./utils.js\";\nimport { HttpService } from \"../common/httpService.js\";\nimport { TestRunCreatePayload } from \"../common/types.js\";\nimport { ServiceErrorMessageConstants } from \"../common/messages.js\";\nimport { Constants } from \"../common/constants.js\";\n\n/**\n * Makes a PATCH call to the Playwright workspaces Test Run API to create or update a test run.\n *\n * @param payload - The request payload (displayName, config, ciConfig, etc.).\n * @returns The parsed JSON response from the API.\n * @throws If the API call fails (non-2xx response).\n */\nexport class PlaywrightServiceApiCall {\n private httpService: HttpService;\n\n constructor(httpService?: HttpService) {\n this.httpService = httpService ?? new HttpService();\n }\n\n async patchTestRunAPI(payload: TestRunCreatePayload): Promise<any> {\n const baseUrl = getTestRunApiUrl();\n const token = getAccessToken();\n if (!token) {\n throw new Error(\"PLAYWRIGHT_SERVICE_ACCESS_TOKEN environment variable is not set.\");\n }\n const url = new URL(baseUrl);\n url.searchParams.set(\"api-version\", Constants.LatestAPIVersion);\n const method = \"PATCH\";\n const data = JSON.stringify(payload);\n const contentType = \"application/merge-patch+json\";\n const correlationId = crypto.randomUUID();\n\n const response = await this.httpService.callAPI(\n method,\n url.toString(),\n data,\n token,\n contentType,\n correlationId,\n );\n if (response.status !== 200) {\n const errorMessage = extractErrorMessage(response?.bodyAsText ?? \"\");\n exitWithFailureMessage(ServiceErrorMessageConstants.FAILED_TO_CREATE_TEST_RUN, errorMessage);\n }\n console.log(\"Test run created successfully.\");\n return response.bodyAsText ? JSON.parse(response.bodyAsText) : {};\n }\n\n async getWorkspaceMetadata(): Promise<any> {\n const baseUrl = getWorkspaceMetaDataApiUrl();\n const token = getAccessToken();\n if (!token) {\n throw new Error(\"PLAYWRIGHT_SERVICE_ACCESS_TOKEN environment variable is not set.\");\n }\n const url = new URL(baseUrl);\n url.searchParams.set(\"api-version\", Constants.LatestAPIVersion);\n const method = \"GET\";\n const correlationId = crypto.randomUUID();\n\n const response = await this.httpService.callAPI(\n method,\n url.toString(),\n null,\n token,\n \"\",\n correlationId,\n );\n if (response.status !== 200) {\n const errorMessage = extractErrorMessage(response?.bodyAsText ?? \"\");\n exitWithFailureMessage(\n ServiceErrorMessageConstants.FAILED_TO_GET_WORKSPACE_DETAILS,\n errorMessage,\n );\n }\n return response.bodyAsText ? JSON.parse(response.bodyAsText) : {};\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { VersionInfo, RunConfig } from "../common/types.js";
|
|
1
|
+
import type { VersionInfo, RunConfig, WorkspaceMetaData } from "../common/types.js";
|
|
2
2
|
import type { TokenCredential } from "@azure/core-auth";
|
|
3
3
|
import { FullConfig } from "@playwright/test";
|
|
4
4
|
import { CIInfo } from "./cIInfoProvider.js";
|
|
@@ -40,4 +40,17 @@ export declare function getTestRunApiUrl(): string;
|
|
|
40
40
|
export declare function isNullOrEmpty(str: string | null | undefined): boolean;
|
|
41
41
|
export declare function getRunName(ciInfo: CIInfo): Promise<string>;
|
|
42
42
|
export declare function extractErrorMessage(responseBody: string): string;
|
|
43
|
+
export declare function getWorkspaceMetaDataApiUrl(): string;
|
|
44
|
+
export declare function getHtmlReporterOutputFolder(config: FullConfig | undefined): string;
|
|
45
|
+
export declare function getContentType(filePath: string): string;
|
|
46
|
+
export declare function calculateOptimalConcurrency(files: Array<{
|
|
47
|
+
size: number;
|
|
48
|
+
}>): number;
|
|
49
|
+
export declare function collectAllFiles(folderPath: string, basePath: string, runIdFolderPrefix?: string): Array<{
|
|
50
|
+
fullPath: string;
|
|
51
|
+
relativePath: string;
|
|
52
|
+
size: number;
|
|
53
|
+
contentType: string;
|
|
54
|
+
}>;
|
|
55
|
+
export declare function getPortalTestRunUrl(workspaceMetadata: WorkspaceMetaData | null): string;
|
|
43
56
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils/utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,9 @@ import { createEntraIdAccessToken } from "../common/entraIdAccessToken.js";
|
|
|
11
11
|
import { CI_PROVIDERS } from "./cIInfoProvider.js";
|
|
12
12
|
import { exec } from "child_process";
|
|
13
13
|
import { getPackageVersionFromFolder } from "./getPackageVersion.js";
|
|
14
|
+
import { readdirSync, statSync } from "fs";
|
|
15
|
+
import { join, relative } from "path";
|
|
16
|
+
import { UploadConstants } from "../common/constants.js";
|
|
14
17
|
// Re-exporting for backward compatibility
|
|
15
18
|
export { getPlaywrightVersion } from "./getPlaywrightVersion.js";
|
|
16
19
|
export { parseJwt } from "./parseJwt.js";
|
|
@@ -257,4 +260,120 @@ export function extractErrorMessage(responseBody) {
|
|
|
257
260
|
return responseBody;
|
|
258
261
|
}
|
|
259
262
|
}
|
|
263
|
+
export function getWorkspaceMetaDataApiUrl() {
|
|
264
|
+
const result = populateValuesFromServiceUrl();
|
|
265
|
+
if (!result?.region || !result?.domain || !result?.accountId) {
|
|
266
|
+
exitWithFailureMessage(ServiceErrorMessageConstants.NO_SERVICE_URL_ERROR);
|
|
267
|
+
}
|
|
268
|
+
const baseUrl = `https://${result?.region}.api.${result?.domain}/playwrightworkspaces/${result?.accountId}`;
|
|
269
|
+
return `${baseUrl}?api-version=${Constants.LatestAPIVersion}`;
|
|
270
|
+
}
|
|
271
|
+
export function getHtmlReporterOutputFolder(config) {
|
|
272
|
+
const defaultFolder = "playwright-report";
|
|
273
|
+
if (!config?.reporter) {
|
|
274
|
+
return defaultFolder;
|
|
275
|
+
}
|
|
276
|
+
for (const reporter of config.reporter) {
|
|
277
|
+
if (Array.isArray(reporter)) {
|
|
278
|
+
const [reporterName, options] = reporter;
|
|
279
|
+
if (reporterName === "html" && options && typeof options === "object") {
|
|
280
|
+
return options.outputFolder || defaultFolder;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else if (typeof reporter === "string" && reporter === "html") {
|
|
284
|
+
return defaultFolder;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return defaultFolder;
|
|
288
|
+
}
|
|
289
|
+
export function getContentType(filePath) {
|
|
290
|
+
const ext = filePath.toLowerCase().split(".").pop();
|
|
291
|
+
const contentTypes = {
|
|
292
|
+
html: "text/html",
|
|
293
|
+
css: "text/css",
|
|
294
|
+
js: "application/javascript",
|
|
295
|
+
json: "application/json",
|
|
296
|
+
png: "image/png",
|
|
297
|
+
jpg: "image/jpeg",
|
|
298
|
+
jpeg: "image/jpeg",
|
|
299
|
+
svg: "image/svg+xml",
|
|
300
|
+
ico: "image/x-icon",
|
|
301
|
+
txt: "text/plain",
|
|
302
|
+
ttf: "font/ttf",
|
|
303
|
+
woff: "font/woff",
|
|
304
|
+
woff2: "font/woff2",
|
|
305
|
+
webmanifest: "application/manifest+json",
|
|
306
|
+
map: "application/json",
|
|
307
|
+
xml: "application/xml",
|
|
308
|
+
pdf: "application/pdf",
|
|
309
|
+
zip: "application/zip",
|
|
310
|
+
};
|
|
311
|
+
return contentTypes[ext || ""] || "application/octet-stream";
|
|
312
|
+
}
|
|
313
|
+
export function calculateOptimalConcurrency(files) {
|
|
314
|
+
const totalFiles = files.length;
|
|
315
|
+
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
316
|
+
const avgFileSize = totalSize / totalFiles;
|
|
317
|
+
let optimalConcurrency;
|
|
318
|
+
if (totalFiles <= 10) {
|
|
319
|
+
optimalConcurrency = Math.min(totalFiles, 10);
|
|
320
|
+
}
|
|
321
|
+
else if (avgFileSize < UploadConstants.SMALL_FILE_THRESHOLD) {
|
|
322
|
+
optimalConcurrency = Math.min(UploadConstants.MAX_CONCURRENCY, Math.max(UploadConstants.BASE_CONCURRENCY, totalFiles / 50));
|
|
323
|
+
}
|
|
324
|
+
else if (totalFiles > 1000) {
|
|
325
|
+
optimalConcurrency = Math.min(UploadConstants.MAX_CONCURRENCY, UploadConstants.BASE_CONCURRENCY + Math.floor(totalFiles / 200));
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
optimalConcurrency = Math.min(UploadConstants.MAX_CONCURRENCY, UploadConstants.BASE_CONCURRENCY);
|
|
329
|
+
}
|
|
330
|
+
return Math.floor(optimalConcurrency);
|
|
331
|
+
}
|
|
332
|
+
export function collectAllFiles(folderPath, basePath, runIdFolderPrefix) {
|
|
333
|
+
const files = [];
|
|
334
|
+
const stack = [folderPath];
|
|
335
|
+
while (stack.length > 0) {
|
|
336
|
+
const currentPath = stack.pop();
|
|
337
|
+
try {
|
|
338
|
+
const items = readdirSync(currentPath);
|
|
339
|
+
for (const item of items) {
|
|
340
|
+
const itemPath = join(currentPath, item);
|
|
341
|
+
const stats = statSync(itemPath);
|
|
342
|
+
if (stats.isDirectory()) {
|
|
343
|
+
stack.push(itemPath);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
let relativePath = relative(basePath, itemPath).split("\\").join("/");
|
|
347
|
+
if (runIdFolderPrefix) {
|
|
348
|
+
relativePath = `${runIdFolderPrefix}/${relativePath}`;
|
|
349
|
+
}
|
|
350
|
+
files.push({
|
|
351
|
+
fullPath: itemPath,
|
|
352
|
+
relativePath,
|
|
353
|
+
size: stats.size,
|
|
354
|
+
contentType: getContentType(itemPath),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return files;
|
|
364
|
+
}
|
|
365
|
+
export function getPortalTestRunUrl(workspaceMetadata) {
|
|
366
|
+
const { subscriptionId, resourceId, name } = workspaceMetadata ?? {};
|
|
367
|
+
if (!subscriptionId || !resourceId || !name) {
|
|
368
|
+
throw new Error("Missing required workspace metadata: subscriptionId, resourceId, and name are required");
|
|
369
|
+
}
|
|
370
|
+
// Extract resource group from resourceId
|
|
371
|
+
const resourceIdParts = resourceId.split("/");
|
|
372
|
+
const resourceGroupIndex = resourceIdParts.findIndex((part) => part.toLowerCase() === "resourcegroups");
|
|
373
|
+
if (resourceGroupIndex === -1 || resourceGroupIndex + 1 >= resourceIdParts.length) {
|
|
374
|
+
throw new Error("Invalid resourceId format: could not extract resource group name");
|
|
375
|
+
}
|
|
376
|
+
const resourceGroupName = resourceIdParts[resourceGroupIndex + 1];
|
|
377
|
+
return `https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/${encodeURIComponent(subscriptionId)}/resourceGroups/${encodeURIComponent(resourceGroupName)}/providers/Microsoft.LoadTestService/playwrightWorkspaces/${encodeURIComponent(name)}/TestRuns`;
|
|
378
|
+
}
|
|
260
379
|
//# sourceMappingURL=utils.js.map
|