@azure/playwright 1.0.1-alpha.20251223.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 +24 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/common/types.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type { ConnectOptions } from \"@playwright/test\";\nimport type { ServiceAuth, ServiceOS, SDKLanguage } from \"./constants.js\";\nimport type { TokenCredential } from \"@azure/identity\";\nimport { CIInfo } from \"../utils/cIInfoProvider.js\";\n\n// Public APIs\n\n/**\n * @public\n *\n * Endpoint options for the service.\n */\nexport type EndpointOptions = {\n /**\n * @public\n *\n * A browser websocket endpoint to connect to.\n */\n wsEndpoint: string;\n};\n\n/**\n * @public\n *\n * Browser connect options for the service. This includes endpoint options and connect options.\n *\n * @example\n * ```\n * import playwright, { test, expect, BrowserType } from \"@playwright/test\";\n * import { getConnectOptions, BrowserConnectOptions } from \"@azure/playwright\";\n *\n * test(\"has title\", async ({ browserName }) => {\n * const { wsEndpoint, options } : BrowserConnectOptions = await getConnectOptions();\n * const browser = await (playwright[browserName] as BrowserType).connect(wsEndpoint, options);\n * const context = await browser.newContext();\n * const page = await context.newPage();\n *\n * await page.goto(\"https://playwright.dev/\");\n * await expect(page).toHaveTitle(/Playwright/);\n *\n * await page.close();\n * await context.close();\n * await browser.close();\n * });\n * ```\n */\nexport type BrowserConnectOptions = EndpointOptions & {\n options: ConnectOptions;\n};\n\n/**\n * @public\n *\n * Additional options for the service.\n */\nexport type PlaywrightServiceAdditionalOptions = {\n /**\n * @public\n *\n * Authentication types supported by Azure Playwright.\n *\n * @defaultValue `ENTRA_ID`\n */\n serviceAuthType?: AuthenticationType;\n\n /**\n * @public\n *\n * Operating system types supported by Azure Playwright.\n *\n * @defaultValue `linux`\n */\n os?: OsType;\n\n /**\n * @public\n *\n * Run id for the test run.\n *\n * @defaultValue `current datetime as ISO string`\n */\n runId?: string;\n\n /**\n * @public\n *\n * Maximum time in milliseconds to wait for the connection to be established.\n *\n * @defaultValue `30000`\n */\n connectTimeout?: number;\n\n /**\n * @public\n *\n * Slows down Playwright operations by the specified amount of milliseconds.\n *\n * @defaultValue `0`\n */\n slowMo?: number;\n\n /**\n * @public\n *\n * Exposes network available on the connecting client to the browser being connected to.\n *\n * @defaultValue `<loopback>`\n */\n exposeNetwork?: string;\n\n /**\n * @public\n *\n * Custom token credential for Entra ID authentication. Learn more at {@link https://github.com/Azure/azure-sdk-for-js/blob/main/documentation/using-azure-identity.md | Using Azure Identity}.\n *\n * @defaultValue `DefaultAzureCredential`\n */\n credential?: TokenCredential;\n /**\n * @public\n *\n * Run name for the test run.\n *\n * @defaultValue `guid`\n */\n runName?: string;\n\n /**\n * @public\n *\n * API Version\n *\n * @defaultValue `2025-09-01`\n */\n apiVersion?: \"2025-09-01\";\n};\n\n/**\n * @public\n *\n * OS Types supported by Azure Playwright.\n */\nexport type OsType = (typeof ServiceOS)[keyof typeof ServiceOS];\n\n/**\n * @public\n *\n * Authentication types supported by Azure Playwright.\n */\nexport type AuthenticationType = (typeof ServiceAuth)[keyof typeof ServiceAuth];\n\n// Internal APIs\n\nexport type JwtPayload = {\n iss?: string;\n sub?: string;\n aud?: string[] | string;\n exp?: number;\n nbf?: number;\n iat?: number;\n jti?: string;\n};\n\nexport type AccessTokenClaims = JwtPayload & {\n pwid?: string;\n};\n\nexport type VersionInfo = {\n major: number;\n minor: number;\n patch: number;\n};\n\nexport type PackageManager = {\n runCommand: (command: string, args: string) => string;\n getVersionFromStdout: (stdout: string) => string;\n};\n\nexport type RunConfig = {\n framework?: RunFramework;\n sdkLanguage?: (typeof SDKLanguage)[keyof typeof SDKLanguage];\n maxWorkers?: number;\n};\n\nexport type RunFramework = {\n name?: string;\n version?: string;\n runnerName?: string;\n};\n\nexport type TestRunCreatePayload = {\n displayName: string;\n config?: RunConfig;\n ciConfig?: CIInfo;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/common/types.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type { ConnectOptions } from \"@playwright/test\";\nimport type { ServiceAuth, ServiceOS, SDKLanguage } from \"./constants.js\";\nimport type { TokenCredential } from \"@azure/identity\";\nimport { CIInfo } from \"../utils/cIInfoProvider.js\";\n\n// Public APIs\n\n/**\n * @public\n *\n * Endpoint options for the service.\n */\nexport type EndpointOptions = {\n /**\n * @public\n *\n * A browser websocket endpoint to connect to.\n */\n wsEndpoint: string;\n};\n\n/**\n * @public\n *\n * Browser connect options for the service. This includes endpoint options and connect options.\n *\n * @example\n * ```\n * import playwright, { test, expect, BrowserType } from \"@playwright/test\";\n * import { getConnectOptions, BrowserConnectOptions } from \"@azure/playwright\";\n *\n * test(\"has title\", async ({ browserName }) => {\n * const { wsEndpoint, options } : BrowserConnectOptions = await getConnectOptions();\n * const browser = await (playwright[browserName] as BrowserType).connect(wsEndpoint, options);\n * const context = await browser.newContext();\n * const page = await context.newPage();\n *\n * await page.goto(\"https://playwright.dev/\");\n * await expect(page).toHaveTitle(/Playwright/);\n *\n * await page.close();\n * await context.close();\n * await browser.close();\n * });\n * ```\n */\nexport type BrowserConnectOptions = EndpointOptions & {\n options: ConnectOptions;\n};\n\n/**\n * @public\n *\n * Additional options for the service.\n */\nexport type PlaywrightServiceAdditionalOptions = {\n /**\n * @public\n *\n * Authentication types supported by Azure Playwright.\n *\n * @defaultValue `ENTRA_ID`\n */\n serviceAuthType?: AuthenticationType;\n\n /**\n * @public\n *\n * Operating system types supported by Azure Playwright.\n *\n * @defaultValue `linux`\n */\n os?: OsType;\n\n /**\n * @public\n *\n * Run id for the test run.\n *\n * @defaultValue `current datetime as ISO string`\n */\n runId?: string;\n\n /**\n * @public\n *\n * Maximum time in milliseconds to wait for the connection to be established.\n *\n * @defaultValue `30000`\n */\n connectTimeout?: number;\n\n /**\n * @public\n *\n * Slows down Playwright operations by the specified amount of milliseconds.\n *\n * @defaultValue `0`\n */\n slowMo?: number;\n\n /**\n * @public\n *\n * Exposes network available on the connecting client to the browser being connected to.\n *\n * @defaultValue `<loopback>`\n */\n exposeNetwork?: string;\n\n /**\n * @public\n *\n * Custom token credential for Entra ID authentication. Learn more at {@link https://github.com/Azure/azure-sdk-for-js/blob/main/documentation/using-azure-identity.md | Using Azure Identity}.\n *\n * @defaultValue `DefaultAzureCredential`\n */\n credential?: TokenCredential;\n /**\n * @public\n *\n * Run name for the test run.\n *\n * @defaultValue `guid`\n */\n runName?: string;\n\n /**\n * @public\n *\n * API Version\n *\n * @defaultValue `2025-09-01`\n */\n apiVersion?: \"2025-09-01\";\n};\n\n/**\n * @public\n *\n * OS Types supported by Azure Playwright.\n */\nexport type OsType = (typeof ServiceOS)[keyof typeof ServiceOS];\n\n/**\n * @public\n *\n * Authentication types supported by Azure Playwright.\n */\nexport type AuthenticationType = (typeof ServiceAuth)[keyof typeof ServiceAuth];\n\n// Internal APIs\n\nexport type JwtPayload = {\n iss?: string;\n sub?: string;\n aud?: string[] | string;\n exp?: number;\n nbf?: number;\n iat?: number;\n jti?: string;\n};\n\nexport type AccessTokenClaims = JwtPayload & {\n pwid?: string;\n};\n\nexport type VersionInfo = {\n major: number;\n minor: number;\n patch: number;\n};\n\nexport type PackageManager = {\n runCommand: (command: string, args: string) => string;\n getVersionFromStdout: (stdout: string) => string;\n};\n\nexport type RunConfig = {\n framework?: RunFramework;\n sdkLanguage?: (typeof SDKLanguage)[keyof typeof SDKLanguage];\n maxWorkers?: number;\n};\n\nexport type RunFramework = {\n name?: string;\n version?: string;\n runnerName?: string;\n};\n\nexport type TestRunCreatePayload = {\n displayName: string;\n config?: RunConfig;\n ciConfig?: CIInfo;\n};\n\nexport type WorkspaceMetaData = {\n id?: string;\n resourceId?: string;\n name?: string;\n state?: string;\n subscriptionId?: string;\n subscriptionState?: string;\n tenantId?: string;\n location?: string;\n regionalAffinity?: string;\n localAuth?: string;\n storageUri?: string;\n};\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure Playwright Reporter
|
|
3
|
+
* Feature that uploads the report generated by the HTML reporter to the customer's storage account and
|
|
4
|
+
* view them in the service portal for faster and easier troubleshooting.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import playwrightReporter from "./playwrightReporter.js";
|
|
9
|
+
export default playwrightReporter;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -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":"AAGA,OAAO,EAAE,gCAAgC,EAAE,MAAM,8CAA8C,CAAC;AAChG,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAGhF;;GAEG;AAEH,MAAM,CAAC,OAAO,OAAO,kBAAkB;IAC7B,MAAM,CAAyB;IAC/B,iBAAiB,GAA6B,IAAI,CAAC;IAE3D;;;;OAIG;IAEH,KAAK,CAAC,OAAO,CAAC,MAAkB;QAC9B,UAAU,CAAC,IAAI,CAAC,2BAA2B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAEvF,4CAA4C;QAC5C,IAAI,CAAC,iCAAiC,CAAC,MAAM,CAAC,CAAC;QAE/C,mEAAmE;QACnE,MAAM,uBAAuB,GAAG,uBAAuB,CAAC,QAAQ,CAAC;QACjE,UAAU,CAAC,IAAI,CAAC,gCAAgC,uBAAuB,CAAC,eAAe,EAAE,CAAC,CAAC;QAC3F,MAAM,kBAAkB,GAAG,uBAAuB,CAAC,eAAe,KAAK,WAAW,CAAC,YAAY,CAAC;QAChG,IAAI,kBAAkB,EAAE,CAAC;YACvB,sBAAsB,CAAC,4BAA4B,CAAC,4BAA4B,CAAC,CAAC;QACpF,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;QACrE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,uCAAuC;QACvC,IAAI,CAAC;YACH,MAAM,0BAA0B,GAAG,IAAI,wBAAwB,EAAE,CAAC;YAClE,IAAI,CAAC,iBAAiB,GAAG,MAAM,0BAA0B,CAAC,oBAAoB,EAAE,CAAC;YACjF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACtE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,UAAU,CAAC,KAAK,CACd,qCAAqC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAChG,CAAC;YACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAED;;OAEG;IAEH,KAAK,CAAC,KAAK;QACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;QACrF,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;IAChC,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,2BAA2B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC9D,UAAU,CAAC,IAAI,CAAC,kDAAkD,YAAY,EAAE,CAAC,CAAC;YAClF,MAAM,cAAc,GAAG,IAAI,gCAAgC,EAAE,CAAC;YAC9D,UAAU,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;YAClE,MAAM,cAAc,CAAC,oCAAoC,CACvD,YAAY,EACZ,IAAI,CAAC,iBAAiB,CACvB,CAAC;YACF,UAAU,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;YAEpF,qCAAqC;YACrC,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBAC3B,MAAM,SAAS,GAAG,mBAAmB,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;gBAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,SAAS,IAAI,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YAC9E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,YAAY,IAAI,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAEO,iCAAiC,CAAC,MAAkB;QAC1D,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAC/E,CAAC;QAED,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;YACxD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACjC,OAAO,QAAQ,KAAK,MAAM,CAAC;YAC7B,CAAC;YACD,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnD,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;YAChC,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,sBAAsB,CAAC,4BAA4B,CAAC,sBAAsB,CAAC,CAAC;QAC9E,CAAC;QAED,UAAU,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;IACnF,CAAC;CACF","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"}
|