@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 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/reporter/index.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,OAAO,kBAAkB,MAAM,yBAAyB,CAAC;AAEzD,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
/**
|
|
4
|
+
* Azure Playwright Reporter
|
|
5
|
+
* Feature that uploads the report generated by the HTML reporter to the customer's storage account and
|
|
6
|
+
* view them in the service portal for faster and easier troubleshooting.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import playwrightReporter from "./playwrightReporter.js";
|
|
11
|
+
export default playwrightReporter;
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/reporter/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC;;;;;;GAMG;AAEH,OAAO,kBAAkB,MAAM,yBAAyB,CAAC;AAEzD,eAAe,kBAAkB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n/**\n * Azure Playwright Reporter\n * Feature that uploads the report generated by the HTML reporter to the customer's storage account and\n * view them in the service portal for faster and easier troubleshooting.\n *\n * @packageDocumentation\n */\n\nimport playwrightReporter from \"./playwrightReporter.js\";\n\nexport default playwrightReporter;\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FullConfig, Reporter } from "@playwright/test/reporter";
|
|
2
|
+
/**
|
|
3
|
+
* Azure Playwright Reporter - Uploads generated Playwright test report folder to Azure Storage.
|
|
4
|
+
*/
|
|
5
|
+
export default class PlaywrightReporter implements Reporter {
|
|
6
|
+
private config;
|
|
7
|
+
private workspaceMetadata;
|
|
8
|
+
/**
|
|
9
|
+
* Called when test run begins. Stores configuration for later use and validates serviceAuthType and
|
|
10
|
+
* retrieves workspace metadata.
|
|
11
|
+
* @param config - Playwright test configuration
|
|
12
|
+
*/
|
|
13
|
+
onBegin(config: FullConfig): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Called when test run ends. Uploads Playwright test report to Azure Storage.
|
|
16
|
+
*/
|
|
17
|
+
onEnd(): Promise<void>;
|
|
18
|
+
private uploadHtmlReport;
|
|
19
|
+
private validateHtmlReporterConfiguration;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=playwrightReporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwrightReporter.d.ts","sourceRoot":"","sources":["../../../src/reporter/playwrightReporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AActE;;GAEG;AAEH,MAAM,CAAC,OAAO,OAAO,kBAAmB,YAAW,QAAQ;IACzD,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,iBAAiB,CAAkC;IAE3D;;;;OAIG;IAEG,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BhD;;OAEG;IAEG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAKd,gBAAgB;IAuB9B,OAAO,CAAC,iCAAiC;CAqB1C"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { PlaywrightReporterStorageManager } from "../utils/playwrightReporterStorageManager.js";
|
|
2
|
+
import { getHtmlReporterOutputFolder, getPortalTestRunUrl, exitWithFailureMessage, } from "../utils/utils.js";
|
|
3
|
+
import { coreLogger } from "../common/logger.js";
|
|
4
|
+
import { PlaywrightServiceConfig } from "../common/playwrightServiceConfig.js";
|
|
5
|
+
import { ServiceAuth } from "../common/constants.js";
|
|
6
|
+
import { ServiceErrorMessageConstants } from "../common/messages.js";
|
|
7
|
+
import { PlaywrightServiceApiCall } from "../utils/playwrightServiceApicall.js";
|
|
8
|
+
/**
|
|
9
|
+
* Azure Playwright Reporter - Uploads generated Playwright test report folder to Azure Storage.
|
|
10
|
+
*/
|
|
11
|
+
export default class PlaywrightReporter {
|
|
12
|
+
config;
|
|
13
|
+
workspaceMetadata = null;
|
|
14
|
+
/**
|
|
15
|
+
* Called when test run begins. Stores configuration for later use and validates serviceAuthType and
|
|
16
|
+
* retrieves workspace metadata.
|
|
17
|
+
* @param config - Playwright test configuration
|
|
18
|
+
*/
|
|
19
|
+
async onBegin(config) {
|
|
20
|
+
coreLogger.info(`Reporter configuration: ${JSON.stringify(config.reporter, null, 2)}`);
|
|
21
|
+
// Validate that HTML reporter is configured
|
|
22
|
+
this.validateHtmlReporterConfiguration(config);
|
|
23
|
+
// Validate that reporter is used only with ENTRA_ID authentication
|
|
24
|
+
const playwrightServiceConfig = PlaywrightServiceConfig.instance;
|
|
25
|
+
coreLogger.info(`Current authentication type: ${playwrightServiceConfig.serviceAuthType}`);
|
|
26
|
+
const isUsingAccessToken = playwrightServiceConfig.serviceAuthType === ServiceAuth.ACCESS_TOKEN;
|
|
27
|
+
if (isUsingAccessToken) {
|
|
28
|
+
exitWithFailureMessage(ServiceErrorMessageConstants.REPORTER_REQUIRES_ENTRA_AUTH);
|
|
29
|
+
}
|
|
30
|
+
coreLogger.info("Authentication validation passed - using ENTRA_ID");
|
|
31
|
+
this.config = config;
|
|
32
|
+
// Get workspace metadata for later use
|
|
33
|
+
try {
|
|
34
|
+
const playwrightServiceApiClient = new PlaywrightServiceApiCall();
|
|
35
|
+
this.workspaceMetadata = await playwrightServiceApiClient.getWorkspaceMetadata();
|
|
36
|
+
process.stdout.write("Initializing reporting for this test run.\n");
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
coreLogger.error(`Failed to get workspace metadata: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
40
|
+
process.stderr.write("Failed to initialize reporting for this test run.\n");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Called when test run ends. Uploads Playwright test report to Azure Storage.
|
|
45
|
+
*/
|
|
46
|
+
async onEnd() {
|
|
47
|
+
process.stdout.write(`Uploading Playwright Test report in Azure storage account.\n`);
|
|
48
|
+
await this.uploadHtmlReport();
|
|
49
|
+
}
|
|
50
|
+
async uploadHtmlReport() {
|
|
51
|
+
try {
|
|
52
|
+
const outputFolder = getHtmlReporterOutputFolder(this.config);
|
|
53
|
+
coreLogger.info(`Resolved Playwright test report output folder: ${outputFolder}`);
|
|
54
|
+
const storageManager = new PlaywrightReporterStorageManager();
|
|
55
|
+
coreLogger.info("Starting Playwright test report upload process");
|
|
56
|
+
await storageManager.uploadPlaywrightHtmlReportAfterTests(outputFolder, this.workspaceMetadata);
|
|
57
|
+
coreLogger.info(`✅ Playwright Test report uploaded successfully to Azure Storage.`);
|
|
58
|
+
// Display portal URL for test report
|
|
59
|
+
if (this.workspaceMetadata) {
|
|
60
|
+
const portalUrl = getPortalTestRunUrl(this.workspaceMetadata);
|
|
61
|
+
process.stdout.write(`You can view test report at: ${portalUrl}\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
66
|
+
process.stderr.write(`❌ Failed to upload Playwright test report: ${errorMessage}\n`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
validateHtmlReporterConfiguration(config) {
|
|
70
|
+
if (!config.reporter || !Array.isArray(config.reporter)) {
|
|
71
|
+
throw new Error(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);
|
|
72
|
+
}
|
|
73
|
+
const hasHtmlReporter = config.reporter.some((reporter) => {
|
|
74
|
+
if (typeof reporter === "string") {
|
|
75
|
+
return reporter === "html";
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(reporter) && reporter.length > 0) {
|
|
78
|
+
return reporter[0] === "html";
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
});
|
|
82
|
+
if (!hasHtmlReporter) {
|
|
83
|
+
exitWithFailureMessage(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED);
|
|
84
|
+
}
|
|
85
|
+
coreLogger.info("HTML reporter validation passed - HTML reporter is configured");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=playwrightReporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwrightReporter.js","sourceRoot":"","sources":["../../../src/reporter/playwrightReporter.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\nimport type { FullConfig, Reporter } from \"@playwright/test/reporter\";\nimport { PlaywrightReporterStorageManager } from \"../utils/playwrightReporterStorageManager.js\";\nimport {\n getHtmlReporterOutputFolder,\n getPortalTestRunUrl,\n exitWithFailureMessage,\n} from \"../utils/utils.js\";\nimport { coreLogger } from \"../common/logger.js\";\nimport { PlaywrightServiceConfig } from \"../common/playwrightServiceConfig.js\";\nimport { ServiceAuth } from \"../common/constants.js\";\nimport { ServiceErrorMessageConstants } from \"../common/messages.js\";\nimport { PlaywrightServiceApiCall } from \"../utils/playwrightServiceApicall.js\";\nimport type { WorkspaceMetaData } from \"../common/types.js\";\n\n/**\n * Azure Playwright Reporter - Uploads generated Playwright test report folder to Azure Storage.\n */\n\nexport default class PlaywrightReporter implements Reporter {\n private config: FullConfig | undefined;\n private workspaceMetadata: WorkspaceMetaData | null = null;\n\n /**\n * Called when test run begins. Stores configuration for later use and validates serviceAuthType and\n * retrieves workspace metadata.\n * @param config - Playwright test configuration\n */\n\n async onBegin(config: FullConfig): Promise<void> {\n coreLogger.info(`Reporter configuration: ${JSON.stringify(config.reporter, null, 2)}`);\n\n // Validate that HTML reporter is configured\n this.validateHtmlReporterConfiguration(config);\n\n // Validate that reporter is used only with ENTRA_ID authentication\n const playwrightServiceConfig = PlaywrightServiceConfig.instance;\n coreLogger.info(`Current authentication type: ${playwrightServiceConfig.serviceAuthType}`);\n const isUsingAccessToken = playwrightServiceConfig.serviceAuthType === ServiceAuth.ACCESS_TOKEN;\n if (isUsingAccessToken) {\n exitWithFailureMessage(ServiceErrorMessageConstants.REPORTER_REQUIRES_ENTRA_AUTH);\n }\n coreLogger.info(\"Authentication validation passed - using ENTRA_ID\");\n this.config = config;\n // Get workspace metadata for later use\n try {\n const playwrightServiceApiClient = new PlaywrightServiceApiCall();\n this.workspaceMetadata = await playwrightServiceApiClient.getWorkspaceMetadata();\n process.stdout.write(\"Initializing reporting for this test run.\\n\");\n } catch (error) {\n coreLogger.error(\n `Failed to get workspace metadata: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n process.stderr.write(\"Failed to initialize reporting for this test run.\\n\");\n }\n }\n\n /**\n * Called when test run ends. Uploads Playwright test report to Azure Storage.\n */\n\n async onEnd(): Promise<void> {\n process.stdout.write(`Uploading Playwright Test report in Azure storage account.\\n`);\n await this.uploadHtmlReport();\n }\n\n private async uploadHtmlReport(): Promise<void> {\n try {\n const outputFolder = getHtmlReporterOutputFolder(this.config);\n coreLogger.info(`Resolved Playwright test report output folder: ${outputFolder}`);\n const storageManager = new PlaywrightReporterStorageManager();\n coreLogger.info(\"Starting Playwright test report upload process\");\n await storageManager.uploadPlaywrightHtmlReportAfterTests(\n outputFolder,\n this.workspaceMetadata,\n );\n coreLogger.info(`✅ Playwright Test report uploaded successfully to Azure Storage.`);\n\n // Display portal URL for test report\n if (this.workspaceMetadata) {\n const portalUrl = getPortalTestRunUrl(this.workspaceMetadata);\n process.stdout.write(`You can view test report at: ${portalUrl}\\n`);\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n process.stderr.write(`❌ Failed to upload Playwright test report: ${errorMessage}\\n`);\n }\n }\n\n private validateHtmlReporterConfiguration(config: FullConfig): void {\n if (!config.reporter || !Array.isArray(config.reporter)) {\n throw new Error(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED.message);\n }\n\n const hasHtmlReporter = config.reporter.some((reporter) => {\n if (typeof reporter === \"string\") {\n return reporter === \"html\";\n }\n if (Array.isArray(reporter) && reporter.length > 0) {\n return reporter[0] === \"html\";\n }\n return false;\n });\n\n if (!hasHtmlReporter) {\n exitWithFailureMessage(ServiceErrorMessageConstants.HTML_REPORTER_REQUIRED);\n }\n\n coreLogger.info(\"HTML reporter validation passed - HTML reporter is configured\");\n }\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TokenCredential } from "@azure/core-auth";
|
|
2
|
+
import type { WorkspaceMetaData } from "../common/types.js";
|
|
3
|
+
export declare class PlaywrightReporterStorageManager {
|
|
4
|
+
uploadHtmlReportFolder(credential: TokenCredential, runId: string, outputFolder: string, workspaceDetails: WorkspaceMetaData): Promise<void>;
|
|
5
|
+
private uploadSasWorkerFile;
|
|
6
|
+
private modifyIndexHtml;
|
|
7
|
+
uploadPlaywrightHtmlReportAfterTests(outputFolderName?: string, workspaceMetadata?: WorkspaceMetaData | null): Promise<void>;
|
|
8
|
+
private uploadFolderInParallel;
|
|
9
|
+
private uploadWithConcurrencyControl;
|
|
10
|
+
private executeWithOptimizedBatching;
|
|
11
|
+
private uploadSingleFileOptimized;
|
|
12
|
+
private performOptimizedUpload;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=playwrightReporterStorageManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwrightReporterStorageManager.d.ts","sourceRoot":"","sources":["../../../src/utils/playwrightReporterStorageManager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAYxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,qBAAa,gCAAgC;IACrC,sBAAsB,CAC1B,UAAU,EAAE,eAAe,EAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,iBAAiB,GAClC,OAAO,CAAC,IAAI,CAAC;YA6CF,mBAAmB;YA+BnB,eAAe;IA2DvB,oCAAoC,CACxC,gBAAgB,CAAC,EAAE,MAAM,EACzB,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,IAAI,GAC3C,OAAO,CAAC,IAAI,CAAC;YA8BF,sBAAsB;YAyEtB,4BAA4B;YAqB5B,4BAA4B;YAuE5B,yBAAyB;YA4CzB,sBAAsB;CAsCrC"}
|
|
@@ -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"]}
|