@wdio/browserstack-service 0.0.0-canary-20260722132416
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/LICENSE-MIT +20 -0
- package/README.md +392 -0
- package/browserstack-service.d.ts +43 -0
- package/build/Percy/Percy-Handler.d.ts +32 -0
- package/build/Percy/Percy-Handler.d.ts.map +1 -0
- package/build/Percy/Percy-Handler.js +165 -0
- package/build/Percy/Percy-Handler.js.map +1 -0
- package/build/Percy/Percy.d.ts +19 -0
- package/build/Percy/Percy.d.ts.map +1 -0
- package/build/Percy/Percy.js +164 -0
- package/build/Percy/Percy.js.map +1 -0
- package/build/Percy/PercyBinary.d.ts +10 -0
- package/build/Percy/PercyBinary.d.ts.map +1 -0
- package/build/Percy/PercyBinary.js +161 -0
- package/build/Percy/PercyBinary.js.map +1 -0
- package/build/Percy/PercyCaptureMap.d.ts +9 -0
- package/build/Percy/PercyCaptureMap.d.ts.map +1 -0
- package/build/Percy/PercyCaptureMap.js +36 -0
- package/build/Percy/PercyCaptureMap.js.map +1 -0
- package/build/Percy/PercyHelper.d.ts +8 -0
- package/build/Percy/PercyHelper.d.ts.map +1 -0
- package/build/Percy/PercyHelper.js +68 -0
- package/build/Percy/PercyHelper.js.map +1 -0
- package/build/Percy/PercyLogger.d.ts +15 -0
- package/build/Percy/PercyLogger.d.ts.map +1 -0
- package/build/Percy/PercyLogger.js +68 -0
- package/build/Percy/PercyLogger.js.map +1 -0
- package/build/Percy/PercySDK.d.ts +4 -0
- package/build/Percy/PercySDK.d.ts.map +1 -0
- package/build/Percy/PercySDK.js +86 -0
- package/build/Percy/PercySDK.js.map +1 -0
- package/build/accessibility-handler.d.ts +52 -0
- package/build/accessibility-handler.d.ts.map +1 -0
- package/build/accessibility-handler.js +361 -0
- package/build/accessibility-handler.js.map +1 -0
- package/build/ai-handler.d.ts +23 -0
- package/build/ai-handler.d.ts.map +1 -0
- package/build/ai-handler.js +184 -0
- package/build/ai-handler.js.map +1 -0
- package/build/bstackLogger.d.ts +16 -0
- package/build/bstackLogger.d.ts.map +1 -0
- package/build/bstackLogger.js +71 -0
- package/build/bstackLogger.js.map +1 -0
- package/build/cleanup.d.ts +9 -0
- package/build/cleanup.d.ts.map +1 -0
- package/build/cleanup.js +101 -0
- package/build/cleanup.js.map +1 -0
- package/build/cli/apiUtils.d.ts +14 -0
- package/build/cli/apiUtils.d.ts.map +1 -0
- package/build/cli/apiUtils.js +25 -0
- package/build/cli/apiUtils.js.map +1 -0
- package/build/cli/cliLogger.d.ts +17 -0
- package/build/cli/cliLogger.d.ts.map +1 -0
- package/build/cli/cliLogger.js +82 -0
- package/build/cli/cliLogger.js.map +1 -0
- package/build/cli/cliUtils.d.ts +64 -0
- package/build/cli/cliUtils.d.ts.map +1 -0
- package/build/cli/cliUtils.js +461 -0
- package/build/cli/cliUtils.js.map +1 -0
- package/build/cli/eventDispatcher.d.ts +28 -0
- package/build/cli/eventDispatcher.d.ts.map +1 -0
- package/build/cli/eventDispatcher.js +49 -0
- package/build/cli/eventDispatcher.js.map +1 -0
- package/build/cli/frameworks/automationFramework.d.ts +89 -0
- package/build/cli/frameworks/automationFramework.d.ts.map +1 -0
- package/build/cli/frameworks/automationFramework.js +132 -0
- package/build/cli/frameworks/automationFramework.js.map +1 -0
- package/build/cli/frameworks/constants/automationFrameworkConstants.d.ts +18 -0
- package/build/cli/frameworks/constants/automationFrameworkConstants.d.ts.map +1 -0
- package/build/cli/frameworks/constants/automationFrameworkConstants.js +18 -0
- package/build/cli/frameworks/constants/automationFrameworkConstants.js.map +1 -0
- package/build/cli/frameworks/constants/testFrameworkConstants.d.ts +43 -0
- package/build/cli/frameworks/constants/testFrameworkConstants.d.ts.map +1 -0
- package/build/cli/frameworks/constants/testFrameworkConstants.js +43 -0
- package/build/cli/frameworks/constants/testFrameworkConstants.js.map +1 -0
- package/build/cli/frameworks/testFramework.d.ts +89 -0
- package/build/cli/frameworks/testFramework.d.ts.map +1 -0
- package/build/cli/frameworks/testFramework.js +126 -0
- package/build/cli/frameworks/testFramework.js.map +1 -0
- package/build/cli/frameworks/wdioAutomationFramework.d.ts +28 -0
- package/build/cli/frameworks/wdioAutomationFramework.d.ts.map +1 -0
- package/build/cli/frameworks/wdioAutomationFramework.js +67 -0
- package/build/cli/frameworks/wdioAutomationFramework.js.map +1 -0
- package/build/cli/frameworks/wdioMochaTestFramework.d.ts +82 -0
- package/build/cli/frameworks/wdioMochaTestFramework.d.ts.map +1 -0
- package/build/cli/frameworks/wdioMochaTestFramework.js +322 -0
- package/build/cli/frameworks/wdioMochaTestFramework.js.map +1 -0
- package/build/cli/grpcClient.d.ts +86 -0
- package/build/cli/grpcClient.d.ts.map +1 -0
- package/build/cli/grpcClient.js +532 -0
- package/build/cli/grpcClient.js.map +1 -0
- package/build/cli/index.d.ts +150 -0
- package/build/cli/index.d.ts.map +1 -0
- package/build/cli/index.js +464 -0
- package/build/cli/index.js.map +1 -0
- package/build/cli/instances/automationFrameworkInstance.d.ts +33 -0
- package/build/cli/instances/automationFrameworkInstance.d.ts.map +1 -0
- package/build/cli/instances/automationFrameworkInstance.js +45 -0
- package/build/cli/instances/automationFrameworkInstance.js.map +1 -0
- package/build/cli/instances/testFrameworkInstance.d.ts +62 -0
- package/build/cli/instances/testFrameworkInstance.d.ts.map +1 -0
- package/build/cli/instances/testFrameworkInstance.js +97 -0
- package/build/cli/instances/testFrameworkInstance.js.map +1 -0
- package/build/cli/instances/trackedContext.d.ts +32 -0
- package/build/cli/instances/trackedContext.d.ts.map +1 -0
- package/build/cli/instances/trackedContext.js +48 -0
- package/build/cli/instances/trackedContext.js.map +1 -0
- package/build/cli/instances/trackedInstance.d.ts +40 -0
- package/build/cli/instances/trackedInstance.d.ts.map +1 -0
- package/build/cli/instances/trackedInstance.js +64 -0
- package/build/cli/instances/trackedInstance.js.map +1 -0
- package/build/cli/modules/accessibilityModule.d.ts +46 -0
- package/build/cli/modules/accessibilityModule.d.ts.map +1 -0
- package/build/cli/modules/accessibilityModule.js +447 -0
- package/build/cli/modules/accessibilityModule.js.map +1 -0
- package/build/cli/modules/automateModule.d.ts +26 -0
- package/build/cli/modules/automateModule.d.ts.map +1 -0
- package/build/cli/modules/automateModule.js +223 -0
- package/build/cli/modules/automateModule.js.map +1 -0
- package/build/cli/modules/baseModule.d.ts +33 -0
- package/build/cli/modules/baseModule.d.ts.map +1 -0
- package/build/cli/modules/baseModule.js +52 -0
- package/build/cli/modules/baseModule.js.map +1 -0
- package/build/cli/modules/observabilityModule.d.ts +22 -0
- package/build/cli/modules/observabilityModule.d.ts.map +1 -0
- package/build/cli/modules/observabilityModule.js +46 -0
- package/build/cli/modules/observabilityModule.js.map +1 -0
- package/build/cli/modules/percyModule.d.ts +19 -0
- package/build/cli/modules/percyModule.d.ts.map +1 -0
- package/build/cli/modules/percyModule.js +78 -0
- package/build/cli/modules/percyModule.js.map +1 -0
- package/build/cli/modules/testHubModule.d.ts +30 -0
- package/build/cli/modules/testHubModule.d.ts.map +1 -0
- package/build/cli/modules/testHubModule.js +233 -0
- package/build/cli/modules/testHubModule.js.map +1 -0
- package/build/cli/modules/webdriverIOModule.d.ts +29 -0
- package/build/cli/modules/webdriverIOModule.d.ts.map +1 -0
- package/build/cli/modules/webdriverIOModule.js +129 -0
- package/build/cli/modules/webdriverIOModule.js.map +1 -0
- package/build/cli/states/automationFrameworkState.d.ts +65 -0
- package/build/cli/states/automationFrameworkState.d.ts.map +1 -0
- package/build/cli/states/automationFrameworkState.js +62 -0
- package/build/cli/states/automationFrameworkState.js.map +1 -0
- package/build/cli/states/hookState.d.ts +45 -0
- package/build/cli/states/hookState.d.ts.map +1 -0
- package/build/cli/states/hookState.js +44 -0
- package/build/cli/states/hookState.js.map +1 -0
- package/build/cli/states/testFrameworkState.d.ts +125 -0
- package/build/cli/states/testFrameworkState.d.ts.map +1 -0
- package/build/cli/states/testFrameworkState.js +116 -0
- package/build/cli/states/testFrameworkState.js.map +1 -0
- package/build/config.d.ts +29 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +52 -0
- package/build/config.js.map +1 -0
- package/build/constants.d.ts +96 -0
- package/build/constants.d.ts.map +1 -0
- package/build/constants.js +156 -0
- package/build/constants.js.map +1 -0
- package/build/crash-reporter.d.ts +18 -0
- package/build/crash-reporter.d.ts.map +1 -0
- package/build/crash-reporter.js +134 -0
- package/build/crash-reporter.js.map +1 -0
- package/build/cucumber-types.d.ts +194 -0
- package/build/cucumber-types.d.ts.map +1 -0
- package/build/cucumber-types.js +2 -0
- package/build/cucumber-types.js.map +1 -0
- package/build/data-store.d.ts +4 -0
- package/build/data-store.d.ts.map +1 -0
- package/build/data-store.js +42 -0
- package/build/data-store.js.map +1 -0
- package/build/exitHandler.d.ts +4 -0
- package/build/exitHandler.d.ts.map +1 -0
- package/build/exitHandler.js +105 -0
- package/build/exitHandler.js.map +1 -0
- package/build/fileStream.d.ts +9 -0
- package/build/fileStream.d.ts.map +1 -0
- package/build/fileStream.js +13 -0
- package/build/fileStream.js.map +1 -0
- package/build/grpc/generated/sdk-messages-accessibility.d.ts +76 -0
- package/build/grpc/generated/sdk-messages-accessibility.d.ts.map +1 -0
- package/build/grpc/generated/sdk-messages-accessibility.js +908 -0
- package/build/grpc/generated/sdk-messages-accessibility.js.map +1 -0
- package/build/grpc/generated/sdk-messages-ai.d.ts +52 -0
- package/build/grpc/generated/sdk-messages-ai.d.ts.map +1 -0
- package/build/grpc/generated/sdk-messages-ai.js +643 -0
- package/build/grpc/generated/sdk-messages-ai.js.map +1 -0
- package/build/grpc/generated/sdk-messages-observability.d.ts +44 -0
- package/build/grpc/generated/sdk-messages-observability.d.ts.map +1 -0
- package/build/grpc/generated/sdk-messages-observability.js +505 -0
- package/build/grpc/generated/sdk-messages-observability.js.map +1 -0
- package/build/grpc/generated/sdk-messages-percy.d.ts +16 -0
- package/build/grpc/generated/sdk-messages-percy.d.ts.map +1 -0
- package/build/grpc/generated/sdk-messages-percy.js +111 -0
- package/build/grpc/generated/sdk-messages-percy.js.map +1 -0
- package/build/grpc/generated/sdk-messages-testhub.d.ts +16 -0
- package/build/grpc/generated/sdk-messages-testhub.d.ts.map +1 -0
- package/build/grpc/generated/sdk-messages-testhub.js +132 -0
- package/build/grpc/generated/sdk-messages-testhub.js.map +1 -0
- package/build/grpc/generated/sdk-messages.d.ts +288 -0
- package/build/grpc/generated/sdk-messages.d.ts.map +1 -0
- package/build/grpc/generated/sdk-messages.js +3958 -0
- package/build/grpc/generated/sdk-messages.js.map +1 -0
- package/build/grpc/generated/sdk.d.ts +282 -0
- package/build/grpc/generated/sdk.d.ts.map +1 -0
- package/build/grpc/generated/sdk.js +197 -0
- package/build/grpc/generated/sdk.js.map +1 -0
- package/build/grpc/index.d.ts +43 -0
- package/build/grpc/index.d.ts.map +1 -0
- package/build/grpc/index.js +154 -0
- package/build/grpc/index.js.map +1 -0
- package/build/index.d.ts +20 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +13 -0
- package/build/index.js.map +1 -0
- package/build/insights-handler.d.ts +76 -0
- package/build/insights-handler.d.ts.map +1 -0
- package/build/insights-handler.js +855 -0
- package/build/insights-handler.js.map +1 -0
- package/build/instrumentation/funnelInstrumentation.d.ts +8 -0
- package/build/instrumentation/funnelInstrumentation.d.ts.map +1 -0
- package/build/instrumentation/funnelInstrumentation.js +235 -0
- package/build/instrumentation/funnelInstrumentation.js.map +1 -0
- package/build/instrumentation/performance/constants.d.ts +151 -0
- package/build/instrumentation/performance/constants.d.ts.map +1 -0
- package/build/instrumentation/performance/constants.js +158 -0
- package/build/instrumentation/performance/constants.js.map +1 -0
- package/build/instrumentation/performance/performance-tester.d.ts +55 -0
- package/build/instrumentation/performance/performance-tester.d.ts.map +1 -0
- package/build/instrumentation/performance/performance-tester.js +403 -0
- package/build/instrumentation/performance/performance-tester.js.map +1 -0
- package/build/launcher.d.ts +49 -0
- package/build/launcher.d.ts.map +1 -0
- package/build/launcher.js +1077 -0
- package/build/launcher.js.map +1 -0
- package/build/log4jsAppender.d.ts +2 -0
- package/build/log4jsAppender.d.ts.map +1 -0
- package/build/log4jsAppender.js +20 -0
- package/build/log4jsAppender.js.map +1 -0
- package/build/logPatcher.d.ts +13 -0
- package/build/logPatcher.d.ts.map +1 -0
- package/build/logPatcher.js +39 -0
- package/build/logPatcher.js.map +1 -0
- package/build/logReportingAPI.d.ts +12 -0
- package/build/logReportingAPI.d.ts.map +1 -0
- package/build/logReportingAPI.js +57 -0
- package/build/logReportingAPI.js.map +1 -0
- package/build/reporter.d.ts +40 -0
- package/build/reporter.d.ts.map +1 -0
- package/build/reporter.js +254 -0
- package/build/reporter.js.map +1 -0
- package/build/request-handler.d.ts +19 -0
- package/build/request-handler.d.ts.map +1 -0
- package/build/request-handler.js +78 -0
- package/build/request-handler.js.map +1 -0
- package/build/scripts/accessibility-scripts.d.ts +26 -0
- package/build/scripts/accessibility-scripts.d.ts.map +1 -0
- package/build/scripts/accessibility-scripts.js +91 -0
- package/build/scripts/accessibility-scripts.js.map +1 -0
- package/build/service.d.ts +68 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +740 -0
- package/build/service.js.map +1 -0
- package/build/testHub/utils.d.ts +10 -0
- package/build/testHub/utils.d.ts.map +1 -0
- package/build/testHub/utils.js +67 -0
- package/build/testHub/utils.js.map +1 -0
- package/build/testOps/featureStats.d.ts +45 -0
- package/build/testOps/featureStats.d.ts.map +1 -0
- package/build/testOps/featureStats.js +117 -0
- package/build/testOps/featureStats.js.map +1 -0
- package/build/testOps/featureUsage.d.ts +22 -0
- package/build/testOps/featureUsage.d.ts.map +1 -0
- package/build/testOps/featureUsage.js +48 -0
- package/build/testOps/featureUsage.js.map +1 -0
- package/build/testOps/listener.d.ts +40 -0
- package/build/testOps/listener.d.ts.map +1 -0
- package/build/testOps/listener.js +269 -0
- package/build/testOps/listener.js.map +1 -0
- package/build/testOps/requestUtils.d.ts +4 -0
- package/build/testOps/requestUtils.d.ts.map +1 -0
- package/build/testOps/requestUtils.js +41 -0
- package/build/testOps/requestUtils.js.map +1 -0
- package/build/testOps/testOpsConfig.d.ts +11 -0
- package/build/testOps/testOpsConfig.d.ts.map +1 -0
- package/build/testOps/testOpsConfig.js +20 -0
- package/build/testOps/testOpsConfig.js.map +1 -0
- package/build/testOps/usageStats.d.ts +404 -0
- package/build/testOps/usageStats.d.ts.map +1 -0
- package/build/testOps/usageStats.js +115 -0
- package/build/testOps/usageStats.js.map +1 -0
- package/build/testorchestration/apply-orchestration.d.ts +7 -0
- package/build/testorchestration/apply-orchestration.d.ts.map +1 -0
- package/build/testorchestration/apply-orchestration.js +43 -0
- package/build/testorchestration/apply-orchestration.js.map +1 -0
- package/build/testorchestration/helpers.d.ts +20 -0
- package/build/testorchestration/helpers.d.ts.map +1 -0
- package/build/testorchestration/helpers.js +413 -0
- package/build/testorchestration/helpers.js.map +1 -0
- package/build/testorchestration/request-utils.d.ts +23 -0
- package/build/testorchestration/request-utils.d.ts.map +1 -0
- package/build/testorchestration/request-utils.js +82 -0
- package/build/testorchestration/request-utils.js.map +1 -0
- package/build/testorchestration/test-ordering-server.d.ts +33 -0
- package/build/testorchestration/test-ordering-server.d.ts.map +1 -0
- package/build/testorchestration/test-ordering-server.js +169 -0
- package/build/testorchestration/test-ordering-server.js.map +1 -0
- package/build/testorchestration/testorcherstrationhandler.d.ts +50 -0
- package/build/testorchestration/testorcherstrationhandler.d.ts.map +1 -0
- package/build/testorchestration/testorcherstrationhandler.js +134 -0
- package/build/testorchestration/testorcherstrationhandler.js.map +1 -0
- package/build/testorchestration/testorcherstrationutils.d.ts +86 -0
- package/build/testorchestration/testorcherstrationutils.d.ts.map +1 -0
- package/build/testorchestration/testorcherstrationutils.js +380 -0
- package/build/testorchestration/testorcherstrationutils.js.map +1 -0
- package/build/types.d.ts +413 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/util.d.ts +241 -0
- package/build/util.d.ts.map +1 -0
- package/build/util.js +1665 -0
- package/build/util.js.map +1 -0
- package/package.json +96 -0
- package/src/proto/browserstack/sdk/v1/sdk-messages-accessibility.proto +71 -0
- package/src/proto/browserstack/sdk/v1/sdk-messages-ai.proto +48 -0
- package/src/proto/browserstack/sdk/v1/sdk-messages-observability.proto +39 -0
- package/src/proto/browserstack/sdk/v1/sdk-messages-percy.proto +10 -0
- package/src/proto/browserstack/sdk/v1/sdk-messages-testhub.proto +10 -0
- package/src/proto/browserstack/sdk/v1/sdk-messages.proto +276 -0
- package/src/proto/browserstack/sdk/v1/sdk.proto +41 -0
package/build/util.js
ADDED
|
@@ -0,0 +1,1665 @@
|
|
|
1
|
+
import { hostname, platform, type, version, arch } from 'node:os';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import zlib from 'node:zlib';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import http from 'node:http';
|
|
6
|
+
import https from 'node:https';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import util from 'node:util';
|
|
9
|
+
import got, { HTTPError } from 'got';
|
|
10
|
+
import gitRepoInfo from 'git-repo-info';
|
|
11
|
+
import gitconfig from 'gitconfiglocal';
|
|
12
|
+
import { FormData } from 'formdata-node';
|
|
13
|
+
import { performance } from 'node:perf_hooks';
|
|
14
|
+
import logPatcher from './logPatcher.js';
|
|
15
|
+
import PerformanceTester from './instrumentation/performance/performance-tester.js';
|
|
16
|
+
import * as PERFORMANCE_SDK_EVENTS from './instrumentation/performance/constants.js';
|
|
17
|
+
import { logBuildError, handleErrorForObservability, handleErrorForAccessibility, getProductMapForBuildStartCall } from './testHub/utils.js';
|
|
18
|
+
import { OrchestrationUtils } from './testorchestration/testorcherstrationutils.js';
|
|
19
|
+
import { BROWSER_DESCRIPTION, UPLOAD_LOGS_ENDPOINT, consoleHolder, BSTACK_A11Y_POLLING_TIMEOUT, TESTOPS_SCREENSHOT_ENV, BROWSERSTACK_TESTHUB_UUID, PERF_MEASUREMENT_ENV, RERUN_ENV, TESTOPS_BUILD_COMPLETED_ENV, BROWSERSTACK_TESTHUB_JWT, BROWSERSTACK_OBSERVABILITY, BROWSERSTACK_TEST_REPORTING, BROWSERSTACK_TEST_PLAN_ID, BROWSERSTACK_ACCESSIBILITY, MAX_GIT_META_DATA_SIZE_IN_BYTES, GIT_META_DATA_TRUNCATED, APP_ALLY_ISSUES_SUMMARY_ENDPOINT, APP_ALLY_ISSUES_ENDPOINT, TEST_REPORTING_PROJECT_NAME, CLI_DEBUG_LOGS_FILE, WDIO_NAMING_PREFIX } from './constants.js';
|
|
20
|
+
import CrashReporter from './crash-reporter.js';
|
|
21
|
+
import { BStackLogger } from './bstackLogger.js';
|
|
22
|
+
import AccessibilityScripts from './scripts/accessibility-scripts.js';
|
|
23
|
+
import UsageStats from './testOps/usageStats.js';
|
|
24
|
+
import TestOpsConfig from './testOps/testOpsConfig.js';
|
|
25
|
+
import APIUtils from './cli/apiUtils.js';
|
|
26
|
+
import tar from 'tar';
|
|
27
|
+
import { fileFromPath } from 'formdata-node/file-from-path';
|
|
28
|
+
const pGitconfig = promisify(gitconfig);
|
|
29
|
+
export const DEFAULT_REQUEST_CONFIG = {
|
|
30
|
+
agent: {
|
|
31
|
+
http: new http.Agent({ keepAlive: true }),
|
|
32
|
+
https: new https.Agent({ keepAlive: true }),
|
|
33
|
+
},
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
'X-BSTACK-OBS': 'true'
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export const COLORS = {
|
|
40
|
+
error: 'red',
|
|
41
|
+
warn: 'yellow',
|
|
42
|
+
info: 'cyanBright',
|
|
43
|
+
debug: 'green',
|
|
44
|
+
trace: 'cyan',
|
|
45
|
+
progress: 'magenta'
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* get browser description for Browserstack service
|
|
49
|
+
* @param cap browser capablities
|
|
50
|
+
*/
|
|
51
|
+
export function getBrowserDescription(cap) {
|
|
52
|
+
cap = cap || {};
|
|
53
|
+
if (cap['bstack:options']) {
|
|
54
|
+
cap = { ...cap, ...cap['bstack:options'] };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* These keys describe the browser the test was run on
|
|
58
|
+
*/
|
|
59
|
+
return BROWSER_DESCRIPTION
|
|
60
|
+
.map((k) => cap[k])
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.join(' ');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* get correct browser capabilities object in both multiremote and normal setups
|
|
66
|
+
* @param browser browser object
|
|
67
|
+
* @param caps browser capbilities object. In case of multiremote, the object itself should have a property named 'capabilities'
|
|
68
|
+
* @param browserName browser name in case of multiremote
|
|
69
|
+
*/
|
|
70
|
+
export function getBrowserCapabilities(browser, caps, browserName) {
|
|
71
|
+
if (!browser.isMultiremote) {
|
|
72
|
+
return { ...browser.capabilities, ...caps };
|
|
73
|
+
}
|
|
74
|
+
const multiCaps = caps;
|
|
75
|
+
const globalCap = browserName && browser.getInstance(browserName) ? browser.getInstance(browserName).capabilities : {};
|
|
76
|
+
const cap = browserName && multiCaps[browserName] ? multiCaps[browserName].capabilities : {};
|
|
77
|
+
return { ...globalCap, ...cap };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* check for browserstack W3C capabilities. Does not support legacy capabilities
|
|
81
|
+
* @param cap browser capabilities
|
|
82
|
+
*/
|
|
83
|
+
export function isBrowserstackCapability(cap) {
|
|
84
|
+
return Boolean(cap &&
|
|
85
|
+
cap['bstack:options'] &&
|
|
86
|
+
// return false if the only cap in bstack:options is wdioService,
|
|
87
|
+
// as that is added by the service and not present in user passed caps
|
|
88
|
+
!(Object.keys(cap['bstack:options']).length === 1 &&
|
|
89
|
+
cap['bstack:options'].wdioService));
|
|
90
|
+
}
|
|
91
|
+
export function getParentSuiteName(fullTitle, testSuiteTitle) {
|
|
92
|
+
const fullTitleWords = fullTitle.split(' ');
|
|
93
|
+
const testSuiteTitleWords = testSuiteTitle.split(' ');
|
|
94
|
+
const shortestLength = Math.min(fullTitleWords.length, testSuiteTitleWords.length);
|
|
95
|
+
let c = 0;
|
|
96
|
+
let parentSuiteName = '';
|
|
97
|
+
while (c < shortestLength && fullTitleWords[c] === testSuiteTitleWords[c]) {
|
|
98
|
+
parentSuiteName += fullTitleWords[c++] + ' ';
|
|
99
|
+
}
|
|
100
|
+
return parentSuiteName.trim();
|
|
101
|
+
}
|
|
102
|
+
function processError(error, fn, args) {
|
|
103
|
+
BStackLogger.error(`Error in executing ${fn.name} with args ${args}: ${error}`);
|
|
104
|
+
let argsString;
|
|
105
|
+
try {
|
|
106
|
+
argsString = JSON.stringify(args);
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
argsString = util.inspect(args, { depth: 2 });
|
|
110
|
+
}
|
|
111
|
+
CrashReporter.uploadCrashReport(`Error in executing ${fn.name} with args ${argsString} : ${error}`, error && error.stack);
|
|
112
|
+
}
|
|
113
|
+
export function o11yErrorHandler(fn) {
|
|
114
|
+
return function (...args) {
|
|
115
|
+
try {
|
|
116
|
+
let functionToHandle = fn;
|
|
117
|
+
if (process.env[PERF_MEASUREMENT_ENV]) {
|
|
118
|
+
functionToHandle = performance.timerify(functionToHandle);
|
|
119
|
+
}
|
|
120
|
+
const result = functionToHandle(...args);
|
|
121
|
+
if (result instanceof Promise) {
|
|
122
|
+
return result.catch(error => processError(error, fn, args));
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
processError(error, fn, args);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function errorHandler(fn) {
|
|
132
|
+
return function (...args) {
|
|
133
|
+
try {
|
|
134
|
+
const functionToHandle = fn;
|
|
135
|
+
const result = functionToHandle(...args);
|
|
136
|
+
if (result instanceof Promise) {
|
|
137
|
+
return result.catch(error => BStackLogger.error(`Error in executing ${fn.name} with args ${args}: ${error}`));
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
BStackLogger.error(`Error in executing ${fn.name} with args ${args}: ${error}`);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export async function nodeRequest(requestType, apiEndpoint, options, apiUrl, timeout = 120000) {
|
|
147
|
+
try {
|
|
148
|
+
const response = await got(`${apiUrl}/${apiEndpoint}`, {
|
|
149
|
+
method: requestType,
|
|
150
|
+
timeout: {
|
|
151
|
+
request: timeout
|
|
152
|
+
},
|
|
153
|
+
...options
|
|
154
|
+
}).json();
|
|
155
|
+
return response;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const isLogUpload = apiEndpoint === UPLOAD_LOGS_ENDPOINT;
|
|
159
|
+
if (error instanceof HTTPError && error.response) {
|
|
160
|
+
const errorMessageJson = error.response.body ? JSON.parse(error.response.body.toString()) : null;
|
|
161
|
+
const errorMessage = errorMessageJson ? errorMessageJson.message : null;
|
|
162
|
+
if (errorMessage) {
|
|
163
|
+
isLogUpload ? BStackLogger.debug(`${errorMessage} - ${error.stack}`) : BStackLogger.error(`${errorMessage} - ${error.stack}`);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
isLogUpload ? BStackLogger.debug(`${error.stack}`) : BStackLogger.error(`${error.stack}`);
|
|
167
|
+
}
|
|
168
|
+
if (isLogUpload) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
if (isLogUpload) {
|
|
175
|
+
BStackLogger.debug(`Failed to fire api request due to ${error} - ${error.stack}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
BStackLogger.debug(`Failed to fire api request due to ${error} - ${error.stack}`);
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export function o11yClassErrorHandler(errorClass) {
|
|
184
|
+
const prototype = errorClass.prototype;
|
|
185
|
+
if (Object.getOwnPropertyNames(prototype).length < 2) {
|
|
186
|
+
return errorClass;
|
|
187
|
+
}
|
|
188
|
+
Object.getOwnPropertyNames(prototype).forEach((methodName) => {
|
|
189
|
+
const method = prototype[methodName];
|
|
190
|
+
if (typeof method === 'function' && methodName !== 'constructor' && methodName !== 'commandWrapper') {
|
|
191
|
+
// In order to preserve this context, need to define like this
|
|
192
|
+
Object.defineProperty(prototype, methodName, {
|
|
193
|
+
writable: true,
|
|
194
|
+
value: function (...args) {
|
|
195
|
+
try {
|
|
196
|
+
const result = (process.env[PERF_MEASUREMENT_ENV] ? performance.timerify(method) : method).call(this, ...args);
|
|
197
|
+
if (result instanceof Promise) {
|
|
198
|
+
return result.catch(error => processError(error, method, args));
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
processError(err, method, args);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
return errorClass;
|
|
210
|
+
}
|
|
211
|
+
export const processTestObservabilityResponse = (response) => {
|
|
212
|
+
if (!response.observability) {
|
|
213
|
+
handleErrorForObservability(null);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!response.observability.success) {
|
|
217
|
+
handleErrorForObservability(response.observability);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
process.env[BROWSERSTACK_OBSERVABILITY] = 'true';
|
|
221
|
+
if (response.observability.options.allow_screenshots) {
|
|
222
|
+
process.env[TESTOPS_SCREENSHOT_ENV] = response.observability.options.allow_screenshots.toString();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
export const performO11ySync = async (browser) => {
|
|
226
|
+
if (isBrowserstackSession(browser)) {
|
|
227
|
+
await browser.execute(`browserstack_executor: ${JSON.stringify({
|
|
228
|
+
action: 'annotate',
|
|
229
|
+
arguments: {
|
|
230
|
+
data: `ObservabilitySync:${Date.now()}`,
|
|
231
|
+
level: 'debug'
|
|
232
|
+
}
|
|
233
|
+
})}`);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
export const jsonifyAccessibilityArray = (dataArray, keyName, valueName) => {
|
|
237
|
+
const result = {};
|
|
238
|
+
dataArray.forEach((element) => {
|
|
239
|
+
result[element[keyName]] = element[valueName];
|
|
240
|
+
});
|
|
241
|
+
return result;
|
|
242
|
+
};
|
|
243
|
+
export const processAccessibilityResponse = (response, options) => {
|
|
244
|
+
if (!response.accessibility) {
|
|
245
|
+
if (options.accessibility === true) {
|
|
246
|
+
handleErrorForAccessibility(null);
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (!response.accessibility.success) {
|
|
251
|
+
handleErrorForAccessibility(response.accessibility);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (response.accessibility.options) {
|
|
255
|
+
const { accessibilityToken, pollingTimeout, scannerVersion } = jsonifyAccessibilityArray(response.accessibility.options.capabilities, 'name', 'value');
|
|
256
|
+
const result = jsonifyAccessibilityArray(response.accessibility.options.capabilities, 'name', 'value');
|
|
257
|
+
const scriptsJson = {
|
|
258
|
+
'scripts': jsonifyAccessibilityArray(response.accessibility.options.scripts, 'name', 'command'),
|
|
259
|
+
'commands': response.accessibility.options.commandsToWrap?.commands ?? [],
|
|
260
|
+
'nonBStackInfraA11yChromeOptions': result['goog:chromeOptions']
|
|
261
|
+
};
|
|
262
|
+
if (scannerVersion) {
|
|
263
|
+
process.env.BSTACK_A11Y_SCANNER_VERSION = scannerVersion;
|
|
264
|
+
BStackLogger.debug(`Accessibility scannerVersion ${scannerVersion}`);
|
|
265
|
+
}
|
|
266
|
+
if (accessibilityToken) {
|
|
267
|
+
process.env.BSTACK_A11Y_JWT = accessibilityToken;
|
|
268
|
+
process.env[BROWSERSTACK_ACCESSIBILITY] = 'true';
|
|
269
|
+
}
|
|
270
|
+
if (pollingTimeout) {
|
|
271
|
+
process.env.BSTACK_A11Y_POLLING_TIMEOUT = pollingTimeout;
|
|
272
|
+
}
|
|
273
|
+
if (scriptsJson) {
|
|
274
|
+
AccessibilityScripts.update(scriptsJson);
|
|
275
|
+
AccessibilityScripts.store();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
export const processLaunchBuildResponse = (response, options) => {
|
|
280
|
+
if (options.testObservability) {
|
|
281
|
+
processTestObservabilityResponse(response);
|
|
282
|
+
}
|
|
283
|
+
processAccessibilityResponse(response, options);
|
|
284
|
+
};
|
|
285
|
+
export const launchTestSession = PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.TESTHUB_EVENTS.START, o11yErrorHandler(async function launchTestSession(options, config, bsConfig, bStackConfig, accessibilityAutomation) {
|
|
286
|
+
const launchBuildUsage = UsageStats.getInstance().launchBuildUsage;
|
|
287
|
+
launchBuildUsage.triggered();
|
|
288
|
+
const data = {
|
|
289
|
+
format: 'json',
|
|
290
|
+
project_name: getObservabilityProject(options, bsConfig.projectName),
|
|
291
|
+
name: getObservabilityBuild(options, bsConfig.buildName),
|
|
292
|
+
build_identifier: bsConfig.buildIdentifier,
|
|
293
|
+
started_at: (new Date()).toISOString(),
|
|
294
|
+
tags: getObservabilityBuildTags(options, bsConfig.buildTag),
|
|
295
|
+
host_info: {
|
|
296
|
+
hostname: hostname(),
|
|
297
|
+
platform: platform(),
|
|
298
|
+
type: type(),
|
|
299
|
+
version: version(),
|
|
300
|
+
arch: arch()
|
|
301
|
+
},
|
|
302
|
+
ci_info: getCiInfo(),
|
|
303
|
+
build_run_identifier: process.env.BROWSERSTACK_BUILD_RUN_IDENTIFIER,
|
|
304
|
+
failed_tests_rerun: process.env[RERUN_ENV] || false,
|
|
305
|
+
version_control: await getGitMetaData(),
|
|
306
|
+
accessibility: {
|
|
307
|
+
settings: options.accessibilityOptions
|
|
308
|
+
},
|
|
309
|
+
browserstackAutomation: shouldAddServiceVersion(config, options.testObservability),
|
|
310
|
+
framework_details: {
|
|
311
|
+
frameworkName: WDIO_NAMING_PREFIX + config.framework,
|
|
312
|
+
frameworkVersion: bsConfig.bstackServiceVersion,
|
|
313
|
+
sdkVersion: bsConfig.bstackServiceVersion,
|
|
314
|
+
language: 'ECMAScript',
|
|
315
|
+
testFramework: {
|
|
316
|
+
name: 'WebdriverIO',
|
|
317
|
+
version: bsConfig.bstackServiceVersion
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
product_map: getProductMapForBuildStartCall(bStackConfig, accessibilityAutomation),
|
|
321
|
+
config: {},
|
|
322
|
+
test_orchestration: OrchestrationUtils.getInstance(config)?.getBuildStartData() || {},
|
|
323
|
+
test_management: {
|
|
324
|
+
test_plan_id: getTestPlanId(options)
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
if (accessibilityAutomation && (isTurboScale(options) || data.browserstackAutomation === false)) {
|
|
328
|
+
data.accessibility.settings ??= {};
|
|
329
|
+
data.accessibility.settings.includeEncodedExtension = true;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
if (Object.keys(CrashReporter.userConfigForReporting).length === 0) {
|
|
333
|
+
CrashReporter.userConfigForReporting = process.env.USER_CONFIG_FOR_REPORTING !== undefined ? JSON.parse(process.env.USER_CONFIG_FOR_REPORTING) : {};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
return BStackLogger.error(`[Crash_Report_Upload] Failed to parse user config while sending build start event due to ${error}`);
|
|
338
|
+
}
|
|
339
|
+
data.config = CrashReporter.userConfigForReporting;
|
|
340
|
+
try {
|
|
341
|
+
const url = `${APIUtils.DATA_ENDPOINT}/api/v2/builds`;
|
|
342
|
+
const response = await got.post(url, {
|
|
343
|
+
...DEFAULT_REQUEST_CONFIG,
|
|
344
|
+
username: getObservabilityUser(options, config),
|
|
345
|
+
password: getObservabilityKey(options, config),
|
|
346
|
+
json: data
|
|
347
|
+
}).json();
|
|
348
|
+
delete data?.accessibility?.settings?.includeEncodedExtension;
|
|
349
|
+
BStackLogger.debug(`[Start_Build] Success response: ${JSON.stringify(response)}`);
|
|
350
|
+
BStackLogger.debug(`Test Plan Id sent in request: ${getTestPlanId(options)}`);
|
|
351
|
+
process.env[TESTOPS_BUILD_COMPLETED_ENV] = 'true';
|
|
352
|
+
if (response.jwt) {
|
|
353
|
+
process.env[BROWSERSTACK_TESTHUB_JWT] = response.jwt;
|
|
354
|
+
}
|
|
355
|
+
if (response.build_hashed_id) {
|
|
356
|
+
process.env[BROWSERSTACK_TESTHUB_UUID] = response.build_hashed_id;
|
|
357
|
+
TestOpsConfig.getInstance().buildHashedId = response.build_hashed_id;
|
|
358
|
+
BStackLogger.info(`Testhub started with id: ${TestOpsConfig.getInstance().buildHashedId}`);
|
|
359
|
+
}
|
|
360
|
+
processLaunchBuildResponse(response, options);
|
|
361
|
+
launchBuildUsage.success();
|
|
362
|
+
return response;
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
if (!error.success) {
|
|
366
|
+
launchBuildUsage.failed(error);
|
|
367
|
+
logBuildError(error);
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}));
|
|
372
|
+
export const validateCapsWithAppA11y = (platformMeta) => {
|
|
373
|
+
/* Check if the current driver platform is eligible for AppAccessibility scan */
|
|
374
|
+
BStackLogger.debug(`platformMeta ${JSON.stringify(platformMeta)}`);
|
|
375
|
+
if (platformMeta?.platform_name &&
|
|
376
|
+
String(platformMeta?.platform_name).toLowerCase() === 'android' &&
|
|
377
|
+
platformMeta?.platform_version &&
|
|
378
|
+
parseInt(platformMeta?.platform_version?.toString()) < 11) {
|
|
379
|
+
BStackLogger.warn('App Accessibility Automation tests are supported on OS version 11 and above for Android devices.');
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
return true;
|
|
383
|
+
};
|
|
384
|
+
export const validateCapsWithA11y = (deviceName, platformMeta, chromeOptions) => {
|
|
385
|
+
/* Check if the current driver platform is eligible for Accessibility scan */
|
|
386
|
+
try {
|
|
387
|
+
if (deviceName) {
|
|
388
|
+
BStackLogger.warn('Accessibility Automation will run only on Desktop browsers.');
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
if (platformMeta?.browser_name?.toLowerCase() !== 'chrome') {
|
|
392
|
+
BStackLogger.warn('Accessibility Automation will run only on Chrome browsers.');
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
const browserVersion = platformMeta?.browser_version;
|
|
396
|
+
if (!isUndefined(browserVersion) && !(browserVersion === 'latest' || parseFloat(browserVersion + '') > 94)) {
|
|
397
|
+
BStackLogger.warn('Accessibility Automation will run only on Chrome browser version greater than 94.');
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
if (chromeOptions?.args?.includes('--headless')) {
|
|
401
|
+
BStackLogger.warn('Accessibility Automation will not run on legacy headless mode. Switch to new headless mode or avoid using headless mode.');
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
BStackLogger.debug(`Exception in checking capabilities compatibility with Accessibility. Error: ${error}`);
|
|
408
|
+
}
|
|
409
|
+
return false;
|
|
410
|
+
};
|
|
411
|
+
export const validateCapsWithNonBstackA11y = (browserName, browserVersion) => {
|
|
412
|
+
if (browserName?.toLowerCase() !== 'chrome') {
|
|
413
|
+
BStackLogger.warn('Accessibility Automation will run only on Chrome browsers.');
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
if (!isUndefined(browserVersion) && !(browserVersion === 'latest' || parseFloat(browserVersion + '') > 100)) {
|
|
417
|
+
BStackLogger.warn('Accessibility Automation will run only on Chrome browser version greater than 100.');
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
return true;
|
|
421
|
+
};
|
|
422
|
+
export const shouldScanTestForAccessibility = (suiteTitle, testTitle, accessibilityOptions, world, isCucumber) => {
|
|
423
|
+
try {
|
|
424
|
+
const includeTags = Array.isArray(accessibilityOptions?.includeTagsInTestingScope) ? accessibilityOptions?.includeTagsInTestingScope : [];
|
|
425
|
+
const excludeTags = Array.isArray(accessibilityOptions?.excludeTagsInTestingScope) ? accessibilityOptions?.excludeTagsInTestingScope : [];
|
|
426
|
+
if (isCucumber) {
|
|
427
|
+
const tagsList = [];
|
|
428
|
+
world?.pickle?.tags.map((tag) => tagsList.push(tag.name));
|
|
429
|
+
const excluded = excludeTags?.some((exclude) => tagsList.includes(exclude));
|
|
430
|
+
const included = includeTags?.length === 0 || includeTags?.some((include) => tagsList.includes(include));
|
|
431
|
+
return !excluded && included;
|
|
432
|
+
}
|
|
433
|
+
const fullTestName = suiteTitle + ' ' + testTitle;
|
|
434
|
+
const excluded = excludeTags?.some((exclude) => fullTestName.includes(exclude));
|
|
435
|
+
const included = includeTags?.length === 0 || includeTags?.some((include) => fullTestName.includes(include));
|
|
436
|
+
return !excluded && included;
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
BStackLogger.debug(`Error while validating test case for accessibility before scanning. Error : ${error}`);
|
|
440
|
+
}
|
|
441
|
+
return false;
|
|
442
|
+
};
|
|
443
|
+
export const isAccessibilityAutomationSession = (accessibilityFlag) => {
|
|
444
|
+
try {
|
|
445
|
+
const hasA11yJwtToken = typeof process.env.BSTACK_A11Y_JWT === 'string' && process.env.BSTACK_A11Y_JWT.length > 0 && process.env.BSTACK_A11Y_JWT !== 'null' && process.env.BSTACK_A11Y_JWT !== 'undefined';
|
|
446
|
+
return accessibilityFlag && hasA11yJwtToken;
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
BStackLogger.debug(`Exception in verifying the Accessibility session with error : ${error}`);
|
|
450
|
+
}
|
|
451
|
+
return false;
|
|
452
|
+
};
|
|
453
|
+
export const isAppAccessibilityAutomationSession = (accessibilityFlag, isAppAutomate) => {
|
|
454
|
+
const accessibilityAutomation = isAccessibilityAutomationSession(accessibilityFlag);
|
|
455
|
+
return accessibilityAutomation && isAppAutomate;
|
|
456
|
+
};
|
|
457
|
+
export const formatString = (template, ...values) => {
|
|
458
|
+
let i = 0;
|
|
459
|
+
if (template === null) {
|
|
460
|
+
return '';
|
|
461
|
+
}
|
|
462
|
+
return template.replace(/%s/g, () => {
|
|
463
|
+
const value = values[i++];
|
|
464
|
+
return value !== null && value !== undefined ? value : '';
|
|
465
|
+
});
|
|
466
|
+
};
|
|
467
|
+
export const _getParamsForAppAccessibility = (commandName) => {
|
|
468
|
+
return {
|
|
469
|
+
'thTestRunUuid': process.env.TEST_ANALYTICS_ID,
|
|
470
|
+
'thBuildUuid': process.env.BROWSERSTACK_TESTHUB_UUID,
|
|
471
|
+
'thJwtToken': process.env.BROWSERSTACK_TESTHUB_JWT,
|
|
472
|
+
'authHeader': process.env.BSTACK_A11Y_JWT,
|
|
473
|
+
'scanTimestamp': Date.now(),
|
|
474
|
+
'method': commandName
|
|
475
|
+
};
|
|
476
|
+
};
|
|
477
|
+
export const performA11yScan = async (isAppAutomate, browser, isBrowserStackSession, isAccessibility, commandName) => {
|
|
478
|
+
return await PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.A11Y_EVENTS.PERFORM_SCAN, async () => {
|
|
479
|
+
if (!isAccessibilityAutomationSession(isAccessibility)) {
|
|
480
|
+
BStackLogger.warn('Not an Accessibility Automation session, cannot perform Accessibility scan.');
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
if (isAppAccessibilityAutomationSession(isAccessibility, isAppAutomate)) {
|
|
485
|
+
const results = await browser.execute(formatString(AccessibilityScripts.performScan, JSON.stringify(_getParamsForAppAccessibility(commandName))), {});
|
|
486
|
+
BStackLogger.debug(util.format(results));
|
|
487
|
+
return results;
|
|
488
|
+
}
|
|
489
|
+
const results = await browser.executeAsync(AccessibilityScripts.performScan, { 'method': commandName || '' });
|
|
490
|
+
BStackLogger.debug(util.format(results));
|
|
491
|
+
return results;
|
|
492
|
+
}
|
|
493
|
+
catch (err) {
|
|
494
|
+
BStackLogger.error('Accessibility Scan could not be performed : ' + err);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
}, { command: commandName })();
|
|
498
|
+
};
|
|
499
|
+
export const getA11yResults = PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.A11Y_EVENTS.GET_RESULTS, async (isAppAutomate, browser, isBrowserStackSession, isAccessibility) => {
|
|
500
|
+
if (!isAccessibilityAutomationSession(isAccessibility)) {
|
|
501
|
+
BStackLogger.warn('Not an Accessibility Automation session, cannot retrieve Accessibility results.');
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
BStackLogger.debug('Performing scan before getting results');
|
|
506
|
+
await performA11yScan(isAppAutomate, browser, isBrowserStackSession, isAccessibility);
|
|
507
|
+
const results = await browser.executeAsync(AccessibilityScripts.getResults);
|
|
508
|
+
return results;
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
BStackLogger.error('No accessibility results were found.');
|
|
512
|
+
BStackLogger.debug(`getA11yResults Failed. Error: ${error}`);
|
|
513
|
+
return [];
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
export const getAppA11yResults = PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.A11Y_EVENTS.GET_RESULTS, async (isAppAutomate, browser, isBrowserStackSession, isAccessibility, sessionId) => {
|
|
517
|
+
if (!isBrowserStackSession) {
|
|
518
|
+
return []; // since we are running only on Automate as of now
|
|
519
|
+
}
|
|
520
|
+
if (!isAppAccessibilityAutomationSession(isAccessibility, isAppAutomate)) {
|
|
521
|
+
BStackLogger.warn('Not an Accessibility Automation session, cannot retrieve Accessibility results summary.');
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
try {
|
|
525
|
+
const apiUrl = `${APIUtils.APP_ALLY_ENDPOINT}/${APP_ALLY_ISSUES_ENDPOINT}`;
|
|
526
|
+
const apiRespone = await getAppA11yResultResponse(apiUrl, isAppAutomate, browser, isBrowserStackSession, isAccessibility, sessionId);
|
|
527
|
+
const result = apiRespone?.data?.data?.issues;
|
|
528
|
+
BStackLogger.debug(`Polling Result: ${JSON.stringify(result)}`);
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
BStackLogger.error('No accessibility summary was found.');
|
|
533
|
+
BStackLogger.debug(`getAppA11yResults Failed. Error: ${error}`);
|
|
534
|
+
return [];
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
export const getAppA11yResultsSummary = PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.A11Y_EVENTS.GET_RESULTS_SUMMARY, async (isAppAutomate, browser, isBrowserStackSession, isAccessibility, sessionId) => {
|
|
538
|
+
if (!isBrowserStackSession) {
|
|
539
|
+
return {}; // since we are running only on Automate as of now
|
|
540
|
+
}
|
|
541
|
+
if (!isAppAccessibilityAutomationSession(isAccessibility, isAppAutomate)) {
|
|
542
|
+
BStackLogger.warn('Not an Accessibility Automation session, cannot retrieve Accessibility results summary.');
|
|
543
|
+
return {};
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
const apiUrl = `${APIUtils.APP_ALLY_ENDPOINT}/${APP_ALLY_ISSUES_SUMMARY_ENDPOINT}`;
|
|
547
|
+
const apiRespone = await getAppA11yResultResponse(apiUrl, isAppAutomate, browser, isBrowserStackSession, isAccessibility, sessionId);
|
|
548
|
+
const result = apiRespone?.data?.data?.summary;
|
|
549
|
+
BStackLogger.debug(`Polling Result: ${JSON.stringify(result)}`);
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
catch {
|
|
553
|
+
BStackLogger.error('No accessibility summary was found.');
|
|
554
|
+
return {};
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
const getAppA11yResultResponse = async (apiUrl, isAppAutomate, browser, isBrowserStackSession, isAccessibility, sessionId) => {
|
|
558
|
+
BStackLogger.debug('Performing scan before getting results summary');
|
|
559
|
+
await performA11yScan(isAppAutomate, browser, isBrowserStackSession, isAccessibility);
|
|
560
|
+
const upperTimeLimit = process.env[BSTACK_A11Y_POLLING_TIMEOUT] ? Date.now() + parseInt(process.env[BSTACK_A11Y_POLLING_TIMEOUT]) * 1000 : Date.now() + 30000;
|
|
561
|
+
const params = { test_run_uuid: process.env.TEST_ANALYTICS_ID, session_id: sessionId, timestamp: Date.now() }; // Query params to pass
|
|
562
|
+
const header = { Authorization: `Bearer ${process.env.BSTACK_A11Y_JWT}` };
|
|
563
|
+
const apiRespone = await pollApi(apiUrl, params, header, upperTimeLimit);
|
|
564
|
+
BStackLogger.debug(`Polling Result: ${JSON.stringify(apiRespone)}`);
|
|
565
|
+
return apiRespone;
|
|
566
|
+
};
|
|
567
|
+
export const getA11yResultsSummary = PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.A11Y_EVENTS.GET_RESULTS_SUMMARY, async (isAppAutomate, browser, isBrowserStackSession, isAccessibility) => {
|
|
568
|
+
if (!isAccessibilityAutomationSession(isAccessibility)) {
|
|
569
|
+
BStackLogger.warn('Not an Accessibility Automation session, cannot retrieve Accessibility results summary.');
|
|
570
|
+
return {};
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
BStackLogger.debug('Performing scan before getting results summary');
|
|
574
|
+
await performA11yScan(isAppAutomate, browser, isBrowserStackSession, isAccessibility);
|
|
575
|
+
const summaryResults = await browser.executeAsync(AccessibilityScripts.getResultsSummary);
|
|
576
|
+
return summaryResults;
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
BStackLogger.error('No accessibility summary was found.');
|
|
580
|
+
return {};
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
export const stopBuildUpstream = PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.TESTHUB_EVENTS.STOP, o11yErrorHandler(async function stopBuildUpstream(killSignal = null) {
|
|
584
|
+
const stopBuildUsage = UsageStats.getInstance().stopBuildUsage;
|
|
585
|
+
stopBuildUsage.triggered();
|
|
586
|
+
if (!process.env[TESTOPS_BUILD_COMPLETED_ENV]) {
|
|
587
|
+
stopBuildUsage.failed('Build is not completed yet');
|
|
588
|
+
return {
|
|
589
|
+
status: 'error',
|
|
590
|
+
message: 'Build is not completed yet'
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const jwtToken = process.env[BROWSERSTACK_TESTHUB_JWT];
|
|
594
|
+
if (!jwtToken) {
|
|
595
|
+
stopBuildUsage.failed('Token/buildID is undefined, build creation might have failed');
|
|
596
|
+
BStackLogger.debug('[STOP_BUILD] Missing Authentication Token/ Build ID');
|
|
597
|
+
return {
|
|
598
|
+
status: 'error',
|
|
599
|
+
message: 'Token/buildID is undefined, build creation might have failed'
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
const data = {
|
|
603
|
+
'finished_at': (new Date()).toISOString(),
|
|
604
|
+
'finished_metadata': [],
|
|
605
|
+
};
|
|
606
|
+
if (killSignal) {
|
|
607
|
+
data.finished_metadata.push({
|
|
608
|
+
reason: 'user_killed',
|
|
609
|
+
signal: killSignal
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
const url = `${APIUtils.DATA_ENDPOINT}/api/v1/builds/${process.env[BROWSERSTACK_TESTHUB_UUID]}/stop`;
|
|
614
|
+
const response = await got.put(url, {
|
|
615
|
+
agent: DEFAULT_REQUEST_CONFIG.agent,
|
|
616
|
+
headers: {
|
|
617
|
+
...DEFAULT_REQUEST_CONFIG.headers,
|
|
618
|
+
'Authorization': `Bearer ${jwtToken}`
|
|
619
|
+
},
|
|
620
|
+
json: data,
|
|
621
|
+
retry: {
|
|
622
|
+
limit: 3,
|
|
623
|
+
methods: ['GET', 'POST']
|
|
624
|
+
}
|
|
625
|
+
}).json();
|
|
626
|
+
BStackLogger.debug(`[STOP_BUILD] Success response: ${JSON.stringify(response)}`);
|
|
627
|
+
stopBuildUsage.success();
|
|
628
|
+
return {
|
|
629
|
+
status: 'success',
|
|
630
|
+
message: ''
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
stopBuildUsage.failed(error);
|
|
635
|
+
BStackLogger.debug(`[STOP_BUILD] Failed. Error: ${error}`);
|
|
636
|
+
return {
|
|
637
|
+
status: 'error',
|
|
638
|
+
message: error.message
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
}));
|
|
642
|
+
export function getCiInfo() {
|
|
643
|
+
const env = process.env;
|
|
644
|
+
// Jenkins
|
|
645
|
+
if ((typeof env.JENKINS_URL === 'string' && env.JENKINS_URL.length > 0) || (typeof env.JENKINS_HOME === 'string' && env.JENKINS_HOME.length > 0)) {
|
|
646
|
+
return {
|
|
647
|
+
name: 'Jenkins',
|
|
648
|
+
build_url: env.BUILD_URL,
|
|
649
|
+
job_name: env.JOB_NAME,
|
|
650
|
+
build_number: env.BUILD_NUMBER
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
// CircleCI
|
|
654
|
+
if (isTrue(env.CI) && isTrue(env.CIRCLECI)) {
|
|
655
|
+
return {
|
|
656
|
+
name: 'CircleCI',
|
|
657
|
+
build_url: env.CIRCLE_BUILD_URL,
|
|
658
|
+
job_name: env.CIRCLE_JOB,
|
|
659
|
+
build_number: env.CIRCLE_BUILD_NUM
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
// Travis CI
|
|
663
|
+
if (isTrue(env.CI) && isTrue(env.TRAVIS)) {
|
|
664
|
+
return {
|
|
665
|
+
name: 'Travis CI',
|
|
666
|
+
build_url: env.TRAVIS_BUILD_WEB_URL,
|
|
667
|
+
job_name: env.TRAVIS_JOB_NAME,
|
|
668
|
+
build_number: env.TRAVIS_BUILD_NUMBER
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
// Codeship
|
|
672
|
+
if (isTrue(env.CI) && env.CI_NAME === 'codeship') {
|
|
673
|
+
return {
|
|
674
|
+
name: 'Codeship',
|
|
675
|
+
build_url: null,
|
|
676
|
+
job_name: null,
|
|
677
|
+
build_number: null
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
// Bitbucket
|
|
681
|
+
if (env.BITBUCKET_BRANCH && env.BITBUCKET_COMMIT) {
|
|
682
|
+
return {
|
|
683
|
+
name: 'Bitbucket',
|
|
684
|
+
build_url: env.BITBUCKET_GIT_HTTP_ORIGIN,
|
|
685
|
+
job_name: null,
|
|
686
|
+
build_number: env.BITBUCKET_BUILD_NUMBER
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
// Drone
|
|
690
|
+
if (isTrue(env.CI) && isTrue(env.DRONE)) {
|
|
691
|
+
return {
|
|
692
|
+
name: 'Drone',
|
|
693
|
+
build_url: env.DRONE_BUILD_LINK,
|
|
694
|
+
job_name: null,
|
|
695
|
+
build_number: env.DRONE_BUILD_NUMBER
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
// Semaphore
|
|
699
|
+
if (isTrue(env.CI) && isTrue(env.SEMAPHORE)) {
|
|
700
|
+
return {
|
|
701
|
+
name: 'Semaphore',
|
|
702
|
+
build_url: env.SEMAPHORE_ORGANIZATION_URL,
|
|
703
|
+
job_name: env.SEMAPHORE_JOB_NAME,
|
|
704
|
+
build_number: env.SEMAPHORE_JOB_ID
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
// GitLab
|
|
708
|
+
if (isTrue(env.CI) && isTrue(env.GITLAB_CI)) {
|
|
709
|
+
return {
|
|
710
|
+
name: 'GitLab',
|
|
711
|
+
build_url: env.CI_JOB_URL,
|
|
712
|
+
job_name: env.CI_JOB_NAME,
|
|
713
|
+
build_number: env.CI_JOB_ID
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
// Buildkite
|
|
717
|
+
if (isTrue(env.CI) && isTrue(env.BUILDKITE)) {
|
|
718
|
+
return {
|
|
719
|
+
name: 'Buildkite',
|
|
720
|
+
build_url: env.BUILDKITE_BUILD_URL,
|
|
721
|
+
job_name: env.BUILDKITE_LABEL || env.BUILDKITE_PIPELINE_NAME,
|
|
722
|
+
build_number: env.BUILDKITE_BUILD_NUMBER
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
// Visual Studio Team Services
|
|
726
|
+
if (isTrue(env.TF_BUILD) && env.TF_BUILD_BUILDNUMBER) {
|
|
727
|
+
return {
|
|
728
|
+
name: 'Visual Studio Team Services',
|
|
729
|
+
build_url: `${env.SYSTEM_TEAMFOUNDATIONSERVERURI}${env.SYSTEM_TEAMPROJECTID}`,
|
|
730
|
+
job_name: env.SYSTEM_DEFINITIONID,
|
|
731
|
+
build_number: env.BUILD_BUILDID
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
// Appveyor
|
|
735
|
+
if (isTrue(env.APPVEYOR)) {
|
|
736
|
+
return {
|
|
737
|
+
name: 'Appveyor',
|
|
738
|
+
build_url: `${env.APPVEYOR_URL}/project/${env.APPVEYOR_ACCOUNT_NAME}/${env.APPVEYOR_PROJECT_SLUG}/builds/${env.APPVEYOR_BUILD_ID}`,
|
|
739
|
+
job_name: env.APPVEYOR_JOB_NAME,
|
|
740
|
+
build_number: env.APPVEYOR_BUILD_NUMBER
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
// Azure CI
|
|
744
|
+
if (env.AZURE_HTTP_USER_AGENT && env.TF_BUILD) {
|
|
745
|
+
return {
|
|
746
|
+
name: 'Azure CI',
|
|
747
|
+
build_url: `${env.SYSTEM_TEAMFOUNDATIONSERVERURI}${env.SYSTEM_TEAMPROJECT}/_build/results?buildId=${env.BUILD_BUILDID}`,
|
|
748
|
+
job_name: env.BUILD_BUILDID,
|
|
749
|
+
build_number: env.BUILD_BUILDID
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
// AWS CodeBuild
|
|
753
|
+
if (env.CODEBUILD_BUILD_ID || env.CODEBUILD_RESOLVED_SOURCE_VERSION || env.CODEBUILD_SOURCE_VERSION) {
|
|
754
|
+
return {
|
|
755
|
+
name: 'AWS CodeBuild',
|
|
756
|
+
build_url: env.CODEBUILD_PUBLIC_BUILD_URL,
|
|
757
|
+
job_name: env.CODEBUILD_BUILD_ID,
|
|
758
|
+
build_number: env.CODEBUILD_BUILD_ID
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
// Bamboo
|
|
762
|
+
if (env.bamboo_buildNumber) {
|
|
763
|
+
return {
|
|
764
|
+
name: 'Bamboo',
|
|
765
|
+
build_url: env.bamboo_buildResultsUrl,
|
|
766
|
+
job_name: env.bamboo_shortJobName,
|
|
767
|
+
build_number: env.bamboo_buildNumber
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
// Wercker
|
|
771
|
+
if (env.WERCKER || env.WERCKER_MAIN_PIPELINE_STARTED) {
|
|
772
|
+
return {
|
|
773
|
+
name: 'Wercker',
|
|
774
|
+
build_url: env.WERCKER_BUILD_URL,
|
|
775
|
+
job_name: env.WERCKER_MAIN_PIPELINE_STARTED ? 'Main Pipeline' : null,
|
|
776
|
+
build_number: env.WERCKER_GIT_COMMIT
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
// Google Cloud
|
|
780
|
+
if (env.GCP_PROJECT || env.GCLOUD_PROJECT || env.GOOGLE_CLOUD_PROJECT) {
|
|
781
|
+
return {
|
|
782
|
+
name: 'Google Cloud',
|
|
783
|
+
build_url: null,
|
|
784
|
+
job_name: env.PROJECT_ID,
|
|
785
|
+
build_number: env.BUILD_ID,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
// Shippable
|
|
789
|
+
if (env.SHIPPABLE) {
|
|
790
|
+
return {
|
|
791
|
+
name: 'Shippable',
|
|
792
|
+
build_url: env.SHIPPABLE_BUILD_URL,
|
|
793
|
+
job_name: env.SHIPPABLE_JOB_ID ? `Job #${env.SHIPPABLE_JOB_ID}` : null,
|
|
794
|
+
build_number: env.SHIPPABLE_BUILD_NUMBER
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
// Netlify
|
|
798
|
+
if (isTrue(env.NETLIFY)) {
|
|
799
|
+
return {
|
|
800
|
+
name: 'Netlify',
|
|
801
|
+
build_url: env.DEPLOY_URL,
|
|
802
|
+
job_name: env.SITE_NAME,
|
|
803
|
+
build_number: env.BUILD_ID
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
// Github Actions
|
|
807
|
+
if (isTrue(env.GITHUB_ACTIONS)) {
|
|
808
|
+
return {
|
|
809
|
+
name: 'GitHub Actions',
|
|
810
|
+
build_url: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`,
|
|
811
|
+
job_name: env.GITHUB_WORKFLOW,
|
|
812
|
+
build_number: env.GITHUB_RUN_ID,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
// Vercel
|
|
816
|
+
if (isTrue(env.CI) && env.VERCEL === '1') {
|
|
817
|
+
return {
|
|
818
|
+
name: 'Vercel',
|
|
819
|
+
build_url: `http://${env.VERCEL_URL}`,
|
|
820
|
+
job_name: null,
|
|
821
|
+
build_number: null,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
// Teamcity
|
|
825
|
+
if (env.TEAMCITY_VERSION) {
|
|
826
|
+
return {
|
|
827
|
+
name: 'Teamcity',
|
|
828
|
+
build_url: null,
|
|
829
|
+
job_name: null,
|
|
830
|
+
build_number: env.BUILD_NUMBER,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
// Concourse
|
|
834
|
+
if (env.CONCOURSE || env.CONCOURSE_URL || env.CONCOURSE_USERNAME || env.CONCOURSE_TEAM) {
|
|
835
|
+
return {
|
|
836
|
+
name: 'Concourse',
|
|
837
|
+
build_url: null,
|
|
838
|
+
job_name: env.BUILD_JOB_NAME || null,
|
|
839
|
+
build_number: env.BUILD_ID || null,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
// GoCD
|
|
843
|
+
if (env.GO_JOB_NAME) {
|
|
844
|
+
return {
|
|
845
|
+
name: 'GoCD',
|
|
846
|
+
build_url: null,
|
|
847
|
+
job_name: env.GO_JOB_NAME,
|
|
848
|
+
build_number: env.GO_PIPELINE_COUNTER,
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
// CodeFresh
|
|
852
|
+
if (env.CF_BUILD_ID) {
|
|
853
|
+
return {
|
|
854
|
+
name: 'CodeFresh',
|
|
855
|
+
build_url: env.CF_BUILD_URL,
|
|
856
|
+
job_name: env.CF_PIPELINE_NAME,
|
|
857
|
+
build_number: env.CF_BUILD_ID,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
// if no matches, return null
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
export async function getGitMetaData() {
|
|
864
|
+
const info = gitRepoInfo();
|
|
865
|
+
if (!info.commonGitDir) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const { remote } = await pGitconfig(info.commonGitDir);
|
|
869
|
+
const remotes = remote ? Object.keys(remote).map(remoteName => ({ name: remoteName, url: remote[remoteName].url })) : [];
|
|
870
|
+
let gitMetaData = {
|
|
871
|
+
name: 'git',
|
|
872
|
+
sha: info.sha,
|
|
873
|
+
short_sha: info.abbreviatedSha,
|
|
874
|
+
branch: info.branch,
|
|
875
|
+
tag: info.tag,
|
|
876
|
+
committer: info.committer,
|
|
877
|
+
committer_date: info.committerDate,
|
|
878
|
+
author: info.author,
|
|
879
|
+
author_date: info.authorDate,
|
|
880
|
+
commit_message: info.commitMessage,
|
|
881
|
+
root: info.root,
|
|
882
|
+
common_git_dir: info.commonGitDir,
|
|
883
|
+
worktree_git_dir: info.worktreeGitDir,
|
|
884
|
+
last_tag: info.lastTag,
|
|
885
|
+
commits_since_last_tag: info.commitsSinceLastTag,
|
|
886
|
+
remotes: remotes
|
|
887
|
+
};
|
|
888
|
+
gitMetaData = checkAndTruncateVCSInfo(gitMetaData);
|
|
889
|
+
return gitMetaData;
|
|
890
|
+
}
|
|
891
|
+
export function getUniqueIdentifier(test, framework) {
|
|
892
|
+
if (framework === 'jasmine') {
|
|
893
|
+
return test.fullName;
|
|
894
|
+
}
|
|
895
|
+
let parentTitle = test.parent;
|
|
896
|
+
// Sometimes parent will be an object instead of a string
|
|
897
|
+
if (typeof parentTitle === 'object') {
|
|
898
|
+
parentTitle = parentTitle.title;
|
|
899
|
+
}
|
|
900
|
+
return `${parentTitle} - ${test.title}`;
|
|
901
|
+
}
|
|
902
|
+
export function getUniqueIdentifierForCucumber(world) {
|
|
903
|
+
return world.pickle.uri + '_' + world.pickle.astNodeIds.join(',');
|
|
904
|
+
}
|
|
905
|
+
export function getCloudProvider(browser) {
|
|
906
|
+
if (browser && 'instances' in browser) {
|
|
907
|
+
// Loop through all instances
|
|
908
|
+
for (const instanceName of browser.instances) {
|
|
909
|
+
const instance = browser[instanceName];
|
|
910
|
+
if (instance.options && instance.options.hostname && instance.options.hostname.includes('browserstack')) {
|
|
911
|
+
return 'browserstack';
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
else if (browser.options && browser.options.hostname && browser.options.hostname.includes('browserstack')) { // Single browser instance
|
|
916
|
+
return 'browserstack';
|
|
917
|
+
}
|
|
918
|
+
return 'unknown_grid';
|
|
919
|
+
}
|
|
920
|
+
export function isBrowserstackSession(browser) {
|
|
921
|
+
return browser && getCloudProvider(browser).toLowerCase() === 'browserstack';
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Sets a BrowserStack annotation with the provided message
|
|
925
|
+
* @param browser - The WebDriver browser instance
|
|
926
|
+
* @param message - The annotation message to set
|
|
927
|
+
* @param isAccessibilityEnabled - Whether accessibility is enabled for the session
|
|
928
|
+
*/
|
|
929
|
+
export async function setBrowserstackAnnotation(browser, message, isAccessibilityEnabled) {
|
|
930
|
+
if (isAccessibilityEnabled && isBrowserstackSession(browser)) {
|
|
931
|
+
await browser.execute(`browserstack_executor: ${JSON.stringify({
|
|
932
|
+
action: 'annotate',
|
|
933
|
+
arguments: {
|
|
934
|
+
data: message,
|
|
935
|
+
level: 'info'
|
|
936
|
+
}
|
|
937
|
+
})}`);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
export function getScenarioExamples(world) {
|
|
941
|
+
const scenario = world.pickle;
|
|
942
|
+
// no examples present
|
|
943
|
+
if ((scenario.astNodeIds && scenario.astNodeIds.length <= 1) || scenario.astNodeIds === undefined) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const pickleId = scenario.astNodeIds[0];
|
|
947
|
+
const examplesId = scenario.astNodeIds[1];
|
|
948
|
+
const gherkinDocumentChildren = world.gherkinDocument.feature?.children;
|
|
949
|
+
let examples = [];
|
|
950
|
+
gherkinDocumentChildren?.forEach(child => {
|
|
951
|
+
if (child.rule) {
|
|
952
|
+
// handle if rule is present
|
|
953
|
+
child.rule.children.forEach(childLevel2 => {
|
|
954
|
+
if (childLevel2.scenario && childLevel2.scenario.id === pickleId && childLevel2.scenario.examples) {
|
|
955
|
+
const passedExamples = childLevel2.scenario.examples.flatMap((val) => (val.tableBody)).find((item) => item.id === examplesId)?.cells.map((val) => (val.value));
|
|
956
|
+
if (passedExamples) {
|
|
957
|
+
examples = passedExamples;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
else if (child.scenario && child.scenario.id === pickleId && child.scenario.examples) {
|
|
963
|
+
// handle if scenario outside rule
|
|
964
|
+
const passedExamples = child.scenario.examples.flatMap((val) => (val.tableBody)).find((item) => item.id === examplesId)?.cells.map((val) => (val.value));
|
|
965
|
+
if (passedExamples) {
|
|
966
|
+
examples = passedExamples;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
if (examples.length) {
|
|
971
|
+
return examples;
|
|
972
|
+
}
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
export function removeAnsiColors(message) {
|
|
976
|
+
if (!message) {
|
|
977
|
+
return '';
|
|
978
|
+
}
|
|
979
|
+
// https://stackoverflow.com/a/29497680
|
|
980
|
+
// eslint-disable-next-line no-control-regex
|
|
981
|
+
return message.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
|
982
|
+
}
|
|
983
|
+
export function getLogTag(eventType) {
|
|
984
|
+
if (eventType === 'TestRunStarted' || eventType === 'TestRunFinished') {
|
|
985
|
+
return 'Test_Upload';
|
|
986
|
+
}
|
|
987
|
+
else if (eventType === 'HookRunStarted' || eventType === 'HookRunFinished') {
|
|
988
|
+
return 'Hook_Upload';
|
|
989
|
+
}
|
|
990
|
+
else if (eventType === 'ScreenshotCreated') {
|
|
991
|
+
return 'Screenshot_Upload';
|
|
992
|
+
}
|
|
993
|
+
else if (eventType === 'LogCreated') {
|
|
994
|
+
return 'Log_Upload';
|
|
995
|
+
}
|
|
996
|
+
return 'undefined';
|
|
997
|
+
}
|
|
998
|
+
// get hierarchy for a particular test (called by reporter for skipped tests)
|
|
999
|
+
export function getHierarchy(fullTitle) {
|
|
1000
|
+
if (!fullTitle) {
|
|
1001
|
+
return [];
|
|
1002
|
+
}
|
|
1003
|
+
return fullTitle.split('.').slice(0, -1);
|
|
1004
|
+
}
|
|
1005
|
+
export function getHookType(hookName) {
|
|
1006
|
+
if (hookName.startsWith('"before each"')) {
|
|
1007
|
+
return 'BEFORE_EACH';
|
|
1008
|
+
}
|
|
1009
|
+
else if (hookName.startsWith('"before all"')) {
|
|
1010
|
+
return 'BEFORE_ALL';
|
|
1011
|
+
}
|
|
1012
|
+
else if (hookName.startsWith('"after each"')) {
|
|
1013
|
+
return 'AFTER_EACH';
|
|
1014
|
+
}
|
|
1015
|
+
else if (hookName.startsWith('"after all"')) {
|
|
1016
|
+
return 'AFTER_ALL';
|
|
1017
|
+
}
|
|
1018
|
+
return 'unknown';
|
|
1019
|
+
}
|
|
1020
|
+
export function isScreenshotCommand(args) {
|
|
1021
|
+
return args.endpoint && args.endpoint.includes('/screenshot');
|
|
1022
|
+
}
|
|
1023
|
+
export function isBStackSession(config) {
|
|
1024
|
+
if (typeof config.user === 'string' && typeof config.key === 'string' && config.key.length === 20) {
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
export function isBrowserstackInfra(config, caps) {
|
|
1030
|
+
// a utility function to check if the hostname is browserstack
|
|
1031
|
+
const isBrowserstack = (str) => {
|
|
1032
|
+
return str.includes('browserstack.com');
|
|
1033
|
+
};
|
|
1034
|
+
if ((config.hostname) && !isBrowserstack(config.hostname)) {
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
if (caps && typeof caps === 'object') {
|
|
1038
|
+
if (Array.isArray(caps)) {
|
|
1039
|
+
for (const capability of caps) {
|
|
1040
|
+
if ((capability.hostname) && !isBrowserstack(capability.hostname)) {
|
|
1041
|
+
return false;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
for (const key in caps) {
|
|
1047
|
+
const capability = caps[key];
|
|
1048
|
+
if ((capability.hostname) && !isBrowserstack(capability.hostname)) {
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (!isBStackSession(config)) {
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
return true;
|
|
1058
|
+
}
|
|
1059
|
+
export function getBrowserStackUserAndKey(config, options) {
|
|
1060
|
+
// Fallback 1: Env variables
|
|
1061
|
+
// Fallback 2: Service variables in wdio.conf.js (that are received inside options object)
|
|
1062
|
+
const envOrServiceVariables = {
|
|
1063
|
+
user: getBrowserStackUser(options),
|
|
1064
|
+
key: getBrowserStackKey(options)
|
|
1065
|
+
};
|
|
1066
|
+
if (isBStackSession(envOrServiceVariables)) {
|
|
1067
|
+
return envOrServiceVariables;
|
|
1068
|
+
}
|
|
1069
|
+
// Fallback 3: Service variables in testObservabilityOptions object
|
|
1070
|
+
// Fallback 4: Service variables in the top level config object
|
|
1071
|
+
const o11yVariables = {
|
|
1072
|
+
user: getObservabilityUser(options, config),
|
|
1073
|
+
key: getObservabilityKey(options, config)
|
|
1074
|
+
};
|
|
1075
|
+
if (isBStackSession(o11yVariables)) {
|
|
1076
|
+
return o11yVariables;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
export function shouldAddServiceVersion(config, testObservability, caps) {
|
|
1080
|
+
if ((config.services && config.services.toString().includes('chromedriver') && testObservability !== false) || !isBrowserstackInfra(config, caps)) {
|
|
1081
|
+
return false;
|
|
1082
|
+
}
|
|
1083
|
+
return true;
|
|
1084
|
+
}
|
|
1085
|
+
export async function batchAndPostEvents(eventUrl, kind, data) {
|
|
1086
|
+
if (!process.env[TESTOPS_BUILD_COMPLETED_ENV]) {
|
|
1087
|
+
throw new Error('Build not completed yet');
|
|
1088
|
+
}
|
|
1089
|
+
const jwtToken = process.env[BROWSERSTACK_TESTHUB_JWT];
|
|
1090
|
+
if (!jwtToken) {
|
|
1091
|
+
throw new Error('Missing authentication Token');
|
|
1092
|
+
}
|
|
1093
|
+
try {
|
|
1094
|
+
const url = `${APIUtils.DATA_ENDPOINT}/${eventUrl}`;
|
|
1095
|
+
const response = await got.post(url, {
|
|
1096
|
+
agent: DEFAULT_REQUEST_CONFIG.agent,
|
|
1097
|
+
headers: {
|
|
1098
|
+
...DEFAULT_REQUEST_CONFIG.headers,
|
|
1099
|
+
'Authorization': `Bearer ${jwtToken}`
|
|
1100
|
+
},
|
|
1101
|
+
json: data,
|
|
1102
|
+
retry: {
|
|
1103
|
+
limit: 3,
|
|
1104
|
+
methods: ['GET', 'POST']
|
|
1105
|
+
}
|
|
1106
|
+
}).json();
|
|
1107
|
+
BStackLogger.debug(`[${kind}] Success response: ${JSON.stringify(response)}`);
|
|
1108
|
+
}
|
|
1109
|
+
catch (error) {
|
|
1110
|
+
BStackLogger.debug(`[${kind}] EXCEPTION IN ${kind} REQUEST TO TEST Reporting and Analytics : ${error}`);
|
|
1111
|
+
throw new Error('Exception in request ' + error);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
export function normalizeTestReportingConfig(_options) {
|
|
1115
|
+
if (!isUndefined(_options.testReporting)) {
|
|
1116
|
+
_options.testObservability = _options.testReporting;
|
|
1117
|
+
}
|
|
1118
|
+
if (!isUndefined(_options.testReportingOptions)) {
|
|
1119
|
+
_options.testObservabilityOptions = _options.testReportingOptions;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
export function normalizeTestReportingEnvVariables() {
|
|
1123
|
+
if (!isUndefined(process.env[BROWSERSTACK_TEST_REPORTING])) {
|
|
1124
|
+
process.env[BROWSERSTACK_OBSERVABILITY] = process.env[BROWSERSTACK_TEST_REPORTING];
|
|
1125
|
+
}
|
|
1126
|
+
if (!isUndefined(process.env[TEST_REPORTING_PROJECT_NAME])) {
|
|
1127
|
+
process.env.TEST_OBSERVABILITY_PROJECT_NAME = process.env[TEST_REPORTING_PROJECT_NAME];
|
|
1128
|
+
}
|
|
1129
|
+
if (!isUndefined(process.env.TEST_REPORTING_BUILD_NAME)) {
|
|
1130
|
+
process.env.TEST_OBSERVABILITY_BUILD_NAME = process.env.TEST_REPORTING_BUILD_NAME;
|
|
1131
|
+
}
|
|
1132
|
+
if (!isUndefined(process.env.TEST_REPORTING_BUILD_TAG)) {
|
|
1133
|
+
process.env.TEST_OBSERVABILITY_BUILD_TAG = process.env.TEST_REPORTING_BUILD_TAG;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
export function getObservabilityUser(options, config) {
|
|
1137
|
+
if (process.env.BROWSERSTACK_USERNAME) {
|
|
1138
|
+
return process.env.BROWSERSTACK_USERNAME;
|
|
1139
|
+
}
|
|
1140
|
+
if (options.testObservabilityOptions && options.testObservabilityOptions.user) {
|
|
1141
|
+
return options.testObservabilityOptions.user;
|
|
1142
|
+
}
|
|
1143
|
+
return config.user;
|
|
1144
|
+
}
|
|
1145
|
+
export function getObservabilityKey(options, config) {
|
|
1146
|
+
if (process.env.BROWSERSTACK_ACCESS_KEY) {
|
|
1147
|
+
return process.env.BROWSERSTACK_ACCESS_KEY;
|
|
1148
|
+
}
|
|
1149
|
+
if (options.testObservabilityOptions && options.testObservabilityOptions.key) {
|
|
1150
|
+
return options.testObservabilityOptions.key;
|
|
1151
|
+
}
|
|
1152
|
+
return config.key;
|
|
1153
|
+
}
|
|
1154
|
+
export function getObservabilityProject(options, bstackProjectName) {
|
|
1155
|
+
if (process.env.TEST_OBSERVABILITY_PROJECT_NAME) {
|
|
1156
|
+
return process.env.TEST_OBSERVABILITY_PROJECT_NAME;
|
|
1157
|
+
}
|
|
1158
|
+
if (options.testObservabilityOptions && options.testObservabilityOptions.projectName) {
|
|
1159
|
+
return options.testObservabilityOptions.projectName;
|
|
1160
|
+
}
|
|
1161
|
+
return bstackProjectName;
|
|
1162
|
+
}
|
|
1163
|
+
export function getObservabilityBuild(options, bstackBuildName) {
|
|
1164
|
+
if (process.env.TEST_OBSERVABILITY_BUILD_NAME) {
|
|
1165
|
+
return process.env.TEST_OBSERVABILITY_BUILD_NAME;
|
|
1166
|
+
}
|
|
1167
|
+
if (options.testObservabilityOptions && options.testObservabilityOptions.buildName) {
|
|
1168
|
+
return options.testObservabilityOptions.buildName;
|
|
1169
|
+
}
|
|
1170
|
+
return bstackBuildName || path.basename(path.resolve(process.cwd()));
|
|
1171
|
+
}
|
|
1172
|
+
export function getObservabilityBuildTags(options, bstackBuildTag) {
|
|
1173
|
+
if (process.env.TEST_OBSERVABILITY_BUILD_TAG) {
|
|
1174
|
+
return process.env.TEST_OBSERVABILITY_BUILD_TAG.split(',');
|
|
1175
|
+
}
|
|
1176
|
+
if (options.testObservabilityOptions && options.testObservabilityOptions.buildTag) {
|
|
1177
|
+
return options.testObservabilityOptions.buildTag;
|
|
1178
|
+
}
|
|
1179
|
+
if (bstackBuildTag) {
|
|
1180
|
+
return [bstackBuildTag];
|
|
1181
|
+
}
|
|
1182
|
+
return [];
|
|
1183
|
+
}
|
|
1184
|
+
export function getTestPlanId(options) {
|
|
1185
|
+
if (process.env[BROWSERSTACK_TEST_PLAN_ID]) {
|
|
1186
|
+
return process.env[BROWSERSTACK_TEST_PLAN_ID];
|
|
1187
|
+
}
|
|
1188
|
+
const CLI_ARG = '--browserstack.testManagementOptions.testPlanId';
|
|
1189
|
+
const argIndex = process.argv.indexOf(CLI_ARG);
|
|
1190
|
+
if (argIndex !== -1 && process.argv[argIndex + 1]) {
|
|
1191
|
+
return process.argv[argIndex + 1];
|
|
1192
|
+
}
|
|
1193
|
+
const argWithEquals = process.argv.find((arg) => arg.startsWith(`${CLI_ARG}=`));
|
|
1194
|
+
if (argWithEquals) {
|
|
1195
|
+
return argWithEquals.split('=')[1];
|
|
1196
|
+
}
|
|
1197
|
+
const testPlanId = options.testManagementOptions?.testPlanId;
|
|
1198
|
+
if (typeof testPlanId === 'string' && testPlanId.trim().length > 0) {
|
|
1199
|
+
return testPlanId.trim();
|
|
1200
|
+
}
|
|
1201
|
+
return undefined;
|
|
1202
|
+
}
|
|
1203
|
+
export function getBrowserStackUser(config) {
|
|
1204
|
+
if (process.env.BROWSERSTACK_USERNAME) {
|
|
1205
|
+
return process.env.BROWSERSTACK_USERNAME;
|
|
1206
|
+
}
|
|
1207
|
+
return config.user;
|
|
1208
|
+
}
|
|
1209
|
+
export function getBrowserStackKey(config) {
|
|
1210
|
+
if (process.env.BROWSERSTACK_ACCESS_KEY) {
|
|
1211
|
+
return process.env.BROWSERSTACK_ACCESS_KEY;
|
|
1212
|
+
}
|
|
1213
|
+
return config.key;
|
|
1214
|
+
}
|
|
1215
|
+
export function isUndefined(value) {
|
|
1216
|
+
let res = (value === undefined || value === null);
|
|
1217
|
+
if (typeof value === 'string') {
|
|
1218
|
+
res = res || value === '';
|
|
1219
|
+
}
|
|
1220
|
+
return res;
|
|
1221
|
+
}
|
|
1222
|
+
export function isTrue(value) {
|
|
1223
|
+
return (value + '').toLowerCase() === 'true';
|
|
1224
|
+
}
|
|
1225
|
+
export function isFalse(value) {
|
|
1226
|
+
return (value + '').toLowerCase() === 'false';
|
|
1227
|
+
}
|
|
1228
|
+
export function frameworkSupportsHook(hook, framework) {
|
|
1229
|
+
if (framework === 'mocha' && (hook === 'before' || hook === 'after' || hook === 'beforeEach' || hook === 'afterEach')) {
|
|
1230
|
+
return true;
|
|
1231
|
+
}
|
|
1232
|
+
if (framework === 'cucumber') {
|
|
1233
|
+
return true;
|
|
1234
|
+
}
|
|
1235
|
+
return false;
|
|
1236
|
+
}
|
|
1237
|
+
export const patchConsoleLogs = o11yErrorHandler(() => {
|
|
1238
|
+
const BSTestOpsPatcher = new logPatcher({});
|
|
1239
|
+
Object.keys(consoleHolder).forEach((method) => {
|
|
1240
|
+
if (!(method in console) || typeof console[method] !== 'function') {
|
|
1241
|
+
BStackLogger.debug(`Skipping method: ${method}, exists: ${method in console}, type: ${typeof console[method]}`);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
const origMethod = console[method].bind(console);
|
|
1245
|
+
// Make sure we don't override Constructors
|
|
1246
|
+
// Arrow functions are not construable
|
|
1247
|
+
if (typeof console[method] === 'function' && method !== 'Console') {
|
|
1248
|
+
console[method] = (...args) => {
|
|
1249
|
+
try {
|
|
1250
|
+
if (!Object.keys(BSTestOpsPatcher).includes(method)) {
|
|
1251
|
+
origMethod(...args);
|
|
1252
|
+
}
|
|
1253
|
+
else {
|
|
1254
|
+
origMethod(...args);
|
|
1255
|
+
BSTestOpsPatcher[method](...args);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
catch (error) {
|
|
1259
|
+
origMethod(...args);
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
});
|
|
1265
|
+
export function getFailureObject(error) {
|
|
1266
|
+
const stack = error.stack;
|
|
1267
|
+
const message = typeof error === 'string' ? error : error.message;
|
|
1268
|
+
const backtrace = stack ? removeAnsiColors(stack.toString()) : '';
|
|
1269
|
+
return {
|
|
1270
|
+
failure: [{ backtrace: [backtrace] }],
|
|
1271
|
+
failure_reason: removeAnsiColors(message.toString()),
|
|
1272
|
+
failure_type: message ? (message.toString().match(/AssertionError/) ? 'AssertionError' : 'UnhandledError') : null
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
export function truncateString(field, truncateSizeInBytes) {
|
|
1276
|
+
try {
|
|
1277
|
+
const bufferSizeInBytes = Buffer.from(GIT_META_DATA_TRUNCATED).length;
|
|
1278
|
+
const fieldBufferObj = Buffer.from(field);
|
|
1279
|
+
const lenOfFieldBufferObj = fieldBufferObj.length;
|
|
1280
|
+
const finalLen = Math.ceil(lenOfFieldBufferObj - truncateSizeInBytes - bufferSizeInBytes);
|
|
1281
|
+
if (finalLen > 0) {
|
|
1282
|
+
const truncatedString = fieldBufferObj.subarray(0, finalLen).toString() + GIT_META_DATA_TRUNCATED;
|
|
1283
|
+
return truncatedString;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
catch (error) {
|
|
1287
|
+
BStackLogger.debug(`Error while truncating field, nothing was truncated here: ${error}`);
|
|
1288
|
+
}
|
|
1289
|
+
return field;
|
|
1290
|
+
}
|
|
1291
|
+
export function getSizeOfJsonObjectInBytes(jsonData) {
|
|
1292
|
+
try {
|
|
1293
|
+
const buffer = Buffer.from(JSON.stringify(jsonData));
|
|
1294
|
+
return buffer.length;
|
|
1295
|
+
}
|
|
1296
|
+
catch (error) {
|
|
1297
|
+
BStackLogger.debug(`Something went wrong while calculating size of JSON object: ${error}`);
|
|
1298
|
+
}
|
|
1299
|
+
return -1;
|
|
1300
|
+
}
|
|
1301
|
+
export function checkAndTruncateVCSInfo(gitMetaData) {
|
|
1302
|
+
const gitMetaDataSizeInBytes = getSizeOfJsonObjectInBytes(gitMetaData);
|
|
1303
|
+
if (gitMetaDataSizeInBytes && gitMetaDataSizeInBytes > MAX_GIT_META_DATA_SIZE_IN_BYTES) {
|
|
1304
|
+
const truncateSize = gitMetaDataSizeInBytes - MAX_GIT_META_DATA_SIZE_IN_BYTES;
|
|
1305
|
+
const truncatedCommitMessage = truncateString(gitMetaData.commit_message, truncateSize);
|
|
1306
|
+
gitMetaData.commit_message = truncatedCommitMessage;
|
|
1307
|
+
BStackLogger.info(`The commit has been truncated. Size of commit after truncation is ${getSizeOfJsonObjectInBytes(gitMetaData) / 1024} KB`);
|
|
1308
|
+
}
|
|
1309
|
+
return gitMetaData;
|
|
1310
|
+
}
|
|
1311
|
+
export const sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1312
|
+
export async function uploadLogs(user, key, clientBuildUuid) {
|
|
1313
|
+
try {
|
|
1314
|
+
if (!user || !key) {
|
|
1315
|
+
BStackLogger.debug('Uploading logs failed due to no credentials');
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
const tmpDir = '/tmp';
|
|
1319
|
+
const tarPath = path.join(tmpDir, 'logs.tar');
|
|
1320
|
+
const tarGzPath = path.join(tmpDir, 'logs.tar.gz');
|
|
1321
|
+
const filesToArchive = [
|
|
1322
|
+
BStackLogger.logFilePath,
|
|
1323
|
+
CLI_DEBUG_LOGS_FILE,
|
|
1324
|
+
].filter(f => fs.existsSync(f));
|
|
1325
|
+
const copiedFileNames = [];
|
|
1326
|
+
for (const f of filesToArchive) {
|
|
1327
|
+
const dest = path.join(tmpDir, path.basename(f));
|
|
1328
|
+
fs.copyFileSync(f, dest);
|
|
1329
|
+
copiedFileNames.push(path.basename(f));
|
|
1330
|
+
}
|
|
1331
|
+
await tar.create({
|
|
1332
|
+
file: tarPath,
|
|
1333
|
+
cwd: tmpDir,
|
|
1334
|
+
portable: true,
|
|
1335
|
+
noDirRecurse: true
|
|
1336
|
+
}, copiedFileNames);
|
|
1337
|
+
await new Promise((resolve, reject) => {
|
|
1338
|
+
const source = fs.createReadStream(tarPath);
|
|
1339
|
+
const dest = fs.createWriteStream(tarGzPath);
|
|
1340
|
+
const gzip = zlib.createGzip({ level: 1 });
|
|
1341
|
+
source.pipe(gzip).pipe(dest);
|
|
1342
|
+
dest.on('finish', resolve);
|
|
1343
|
+
dest.on('error', reject);
|
|
1344
|
+
});
|
|
1345
|
+
const formData = new FormData();
|
|
1346
|
+
const file = await fileFromPath(tarGzPath);
|
|
1347
|
+
formData.append('data', file, 'logs.tar.gz');
|
|
1348
|
+
formData.append('clientBuildUuid', clientBuildUuid);
|
|
1349
|
+
const requestOptions = {
|
|
1350
|
+
body: formData,
|
|
1351
|
+
username: user,
|
|
1352
|
+
password: key
|
|
1353
|
+
};
|
|
1354
|
+
const response = await nodeRequest('POST', UPLOAD_LOGS_ENDPOINT, requestOptions, APIUtils.UPLOAD_LOGS_ADDRESS);
|
|
1355
|
+
fs.unlinkSync(tarPath);
|
|
1356
|
+
fs.unlinkSync(tarGzPath);
|
|
1357
|
+
for (const f of copiedFileNames) {
|
|
1358
|
+
const filePath = path.join(tmpDir, f);
|
|
1359
|
+
if (fs.existsSync(filePath)) {
|
|
1360
|
+
fs.unlinkSync(filePath);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
// Delete the SDK CLI log file after upload
|
|
1364
|
+
if (fs.existsSync(CLI_DEBUG_LOGS_FILE)) {
|
|
1365
|
+
fs.unlinkSync(CLI_DEBUG_LOGS_FILE);
|
|
1366
|
+
}
|
|
1367
|
+
return response;
|
|
1368
|
+
}
|
|
1369
|
+
catch (error) {
|
|
1370
|
+
BStackLogger.error(`Error while uploading logs: ${getErrorString(error)}`);
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
export const isObject = (object) => {
|
|
1375
|
+
return object !== null && typeof object === 'object' && !Array.isArray(object);
|
|
1376
|
+
};
|
|
1377
|
+
export const ObjectsAreEqual = (object1, object2) => {
|
|
1378
|
+
const objectKeys1 = Object.keys(object1);
|
|
1379
|
+
const objectKeys2 = Object.keys(object2);
|
|
1380
|
+
if (objectKeys1.length !== objectKeys2.length) {
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
for (const key of objectKeys1) {
|
|
1384
|
+
const value1 = object1[key];
|
|
1385
|
+
const value2 = object2[key];
|
|
1386
|
+
const isBothAreObjects = isObject(value1) && isObject(value2);
|
|
1387
|
+
if ((isBothAreObjects && !ObjectsAreEqual(value1, value2)) || (!isBothAreObjects && value1 !== value2)) {
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
return true;
|
|
1392
|
+
};
|
|
1393
|
+
export const getPlatformVersion = o11yErrorHandler(function getPlatformVersion(caps, userCaps) {
|
|
1394
|
+
if (!caps && !userCaps) {
|
|
1395
|
+
return undefined;
|
|
1396
|
+
}
|
|
1397
|
+
const bstackOptions = (userCaps)?.['bstack:options'];
|
|
1398
|
+
const keys = ['platformVersion', 'platform_version', 'osVersion', 'os_version'];
|
|
1399
|
+
for (const key of keys) {
|
|
1400
|
+
if (caps?.[key]) {
|
|
1401
|
+
BStackLogger.debug(`Got ${key} from driver caps`);
|
|
1402
|
+
return String(caps?.[key]);
|
|
1403
|
+
}
|
|
1404
|
+
else if (bstackOptions && bstackOptions?.[key]) {
|
|
1405
|
+
BStackLogger.debug(`Got ${key} from user bstack options`);
|
|
1406
|
+
return String(bstackOptions?.[key]);
|
|
1407
|
+
}
|
|
1408
|
+
else if (userCaps[key]) {
|
|
1409
|
+
BStackLogger.debug(`Got ${key} from user caps`);
|
|
1410
|
+
return String(userCaps[key]);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return undefined;
|
|
1414
|
+
});
|
|
1415
|
+
export const isObjectEmpty = (objectName) => {
|
|
1416
|
+
return (objectName &&
|
|
1417
|
+
Object.keys(objectName).length === 0 &&
|
|
1418
|
+
objectName.constructor === Object);
|
|
1419
|
+
};
|
|
1420
|
+
export const getErrorString = (err) => {
|
|
1421
|
+
if (!err) {
|
|
1422
|
+
return undefined;
|
|
1423
|
+
}
|
|
1424
|
+
if (typeof err === 'string') {
|
|
1425
|
+
return err; // works, `e` narrowed to string
|
|
1426
|
+
}
|
|
1427
|
+
else if (err instanceof Error) {
|
|
1428
|
+
return err.message; // works, `e` narrowed to Error
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
export function isTurboScale(options) {
|
|
1432
|
+
return Boolean(options?.turboScale);
|
|
1433
|
+
}
|
|
1434
|
+
export function getObservabilityProduct(options, isAppAutomate) {
|
|
1435
|
+
return isAppAutomate
|
|
1436
|
+
? 'app-automate'
|
|
1437
|
+
: (isTurboScale(options) ? 'turboscale' : 'automate');
|
|
1438
|
+
}
|
|
1439
|
+
export const hasBrowserName = (cap) => {
|
|
1440
|
+
if (!cap || !cap.capabilities) {
|
|
1441
|
+
return false;
|
|
1442
|
+
}
|
|
1443
|
+
const browserStackCapabilities = cap.capabilities;
|
|
1444
|
+
return browserStackCapabilities.browserName !== undefined;
|
|
1445
|
+
};
|
|
1446
|
+
export const isValidCapsForHealing = (caps) => {
|
|
1447
|
+
// Get all capability values
|
|
1448
|
+
const capValues = Object.values(caps);
|
|
1449
|
+
// Check if there are any capabilities and if at least one has a browser name
|
|
1450
|
+
return capValues.length > 0 && capValues.some(hasBrowserName);
|
|
1451
|
+
};
|
|
1452
|
+
export async function pollApi(url, params, headers, upperLimit, startTime = Date.now()) {
|
|
1453
|
+
params.timestamp = Math.round(Date.now() / 1000);
|
|
1454
|
+
BStackLogger.debug(`current timestamp ${params.timestamp}`);
|
|
1455
|
+
try {
|
|
1456
|
+
const response = await got(url, {
|
|
1457
|
+
searchParams: params,
|
|
1458
|
+
headers,
|
|
1459
|
+
});
|
|
1460
|
+
const responseData = JSON.parse(response.body);
|
|
1461
|
+
return {
|
|
1462
|
+
data: responseData,
|
|
1463
|
+
headers: response.headers,
|
|
1464
|
+
message: 'Polling succeeded.',
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
catch (error) {
|
|
1468
|
+
if (error.response && error.response.statusCode === 404) {
|
|
1469
|
+
const nextPollTime = parseInt(error.response.headers.next_poll_time, 10) * 1000;
|
|
1470
|
+
BStackLogger.debug(`timeInMillis ${nextPollTime}`);
|
|
1471
|
+
if (isNaN(nextPollTime)) {
|
|
1472
|
+
BStackLogger.warn('Invalid or missing `nextPollTime` header. Stopping polling.');
|
|
1473
|
+
return {
|
|
1474
|
+
data: {},
|
|
1475
|
+
headers: error.response.headers,
|
|
1476
|
+
message: 'Invalid nextPollTime header value. Polling stopped.',
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
const elapsedTime = nextPollTime - Date.now();
|
|
1480
|
+
BStackLogger.debug(`elapsedTime ${elapsedTime} timeInMillis ${nextPollTime} upperLimit ${upperLimit}`);
|
|
1481
|
+
// Stop polling if the upper time limit is reached
|
|
1482
|
+
if (nextPollTime > upperLimit) {
|
|
1483
|
+
BStackLogger.warn('Polling stopped due to upper time limit.');
|
|
1484
|
+
return {
|
|
1485
|
+
data: {},
|
|
1486
|
+
headers: error.response.headers,
|
|
1487
|
+
message: 'Polling stopped due to upper time limit.',
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
BStackLogger.debug(`Polling again in ${elapsedTime}ms with params:`, params);
|
|
1491
|
+
// Wait for the specified time and poll again
|
|
1492
|
+
await new Promise((resolve) => setTimeout(resolve, elapsedTime));
|
|
1493
|
+
return pollApi(url, params, headers, upperLimit, startTime);
|
|
1494
|
+
}
|
|
1495
|
+
else if (error.response) {
|
|
1496
|
+
throw {
|
|
1497
|
+
data: {},
|
|
1498
|
+
headers: {},
|
|
1499
|
+
message: error.response.body ? JSON.parse(error.response.body).message : 'Unknown error',
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
else {
|
|
1503
|
+
BStackLogger.error(`Unexpected error occurred: ${error}`);
|
|
1504
|
+
return { data: {}, headers: {}, message: 'Unexpected error occurred.' };
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
export function getBooleanValueFromString(value) {
|
|
1509
|
+
if (!value) {
|
|
1510
|
+
return false;
|
|
1511
|
+
}
|
|
1512
|
+
return ['true'].includes(value.trim().toLowerCase());
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Validate boolean value from mixed types (boolean or string)
|
|
1516
|
+
* Only accepts true boolean or string "true" (case insensitive)
|
|
1517
|
+
*/
|
|
1518
|
+
export function isValidEnabledValue(value) {
|
|
1519
|
+
if (value === undefined || value === null) {
|
|
1520
|
+
return false;
|
|
1521
|
+
}
|
|
1522
|
+
if (typeof value === 'boolean') {
|
|
1523
|
+
return value === true;
|
|
1524
|
+
}
|
|
1525
|
+
if (typeof value === 'string') {
|
|
1526
|
+
return getBooleanValueFromString(value);
|
|
1527
|
+
}
|
|
1528
|
+
return false;
|
|
1529
|
+
}
|
|
1530
|
+
export function mergeDeep(target, ...sources) {
|
|
1531
|
+
if (!sources.length) {
|
|
1532
|
+
return target;
|
|
1533
|
+
}
|
|
1534
|
+
const source = sources.shift();
|
|
1535
|
+
if (isObject(target) && isObject(source)) {
|
|
1536
|
+
for (const key in source) {
|
|
1537
|
+
const sourceValue = source[key];
|
|
1538
|
+
const targetValue = target[key];
|
|
1539
|
+
if (isObject(sourceValue)) {
|
|
1540
|
+
if (!targetValue || !isObject(targetValue)) {
|
|
1541
|
+
target[key] = {};
|
|
1542
|
+
}
|
|
1543
|
+
mergeDeep(target[key], sourceValue);
|
|
1544
|
+
}
|
|
1545
|
+
else {
|
|
1546
|
+
target[key] = sourceValue;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
return mergeDeep(target, ...sources);
|
|
1551
|
+
}
|
|
1552
|
+
export function mergeChromeOptions(base, override) {
|
|
1553
|
+
const merged = { ...base };
|
|
1554
|
+
if (override.args) {
|
|
1555
|
+
merged.args = [...(base.args || []), ...override.args];
|
|
1556
|
+
}
|
|
1557
|
+
if (override.extensions) {
|
|
1558
|
+
merged.extensions = [...(base.extensions || []), ...override.extensions];
|
|
1559
|
+
}
|
|
1560
|
+
if (override.prefs) {
|
|
1561
|
+
merged.prefs = mergeDeep({ ...(base.prefs || {}) }, override.prefs);
|
|
1562
|
+
}
|
|
1563
|
+
return merged;
|
|
1564
|
+
}
|
|
1565
|
+
export function isNullOrEmpty(string) {
|
|
1566
|
+
return !string || string.trim() === '';
|
|
1567
|
+
}
|
|
1568
|
+
export function isHash(entity) {
|
|
1569
|
+
return Boolean(entity && typeof (entity) === 'object' && !Array.isArray(entity));
|
|
1570
|
+
}
|
|
1571
|
+
export function nestedKeyValue(hash, keys) {
|
|
1572
|
+
return keys.reduce((hash, key) => (isHash(hash) ? hash[key] : undefined), hash);
|
|
1573
|
+
}
|
|
1574
|
+
export function removeDir(dir) {
|
|
1575
|
+
const list = fs.readdirSync(dir);
|
|
1576
|
+
for (let i = 0; i < list.length; i++) {
|
|
1577
|
+
const filename = path.join(dir, list[i]);
|
|
1578
|
+
const stat = fs.statSync(filename);
|
|
1579
|
+
if (filename === '.' || filename === '..') {
|
|
1580
|
+
// pass these files
|
|
1581
|
+
}
|
|
1582
|
+
else if (stat.isDirectory()) {
|
|
1583
|
+
// rmdir recursively
|
|
1584
|
+
removeDir(filename);
|
|
1585
|
+
}
|
|
1586
|
+
else {
|
|
1587
|
+
// rm filename
|
|
1588
|
+
fs.unlinkSync(filename);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
fs.rmdirSync(dir);
|
|
1592
|
+
}
|
|
1593
|
+
export function createDir(dir) {
|
|
1594
|
+
if (fs.existsSync(dir)) {
|
|
1595
|
+
removeDir(dir);
|
|
1596
|
+
}
|
|
1597
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1598
|
+
}
|
|
1599
|
+
export function isWritable(dirPath) {
|
|
1600
|
+
try {
|
|
1601
|
+
fs.accessSync(dirPath, fs.constants.W_OK);
|
|
1602
|
+
return true;
|
|
1603
|
+
}
|
|
1604
|
+
catch {
|
|
1605
|
+
return false;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
export function setReadWriteAccess(dirPath) {
|
|
1609
|
+
try {
|
|
1610
|
+
fs.chmodSync(dirPath, 0o666);
|
|
1611
|
+
BStackLogger.debug(`Directory ${dirPath} is now read/write accessible.`);
|
|
1612
|
+
}
|
|
1613
|
+
catch (err) {
|
|
1614
|
+
BStackLogger.error(`Failed to set directory access: ${err.stack}`);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
export function getMochaTestHierarchy(test) {
|
|
1618
|
+
const value = [];
|
|
1619
|
+
if (test.ctx && test.ctx.test) {
|
|
1620
|
+
// If we already have the parent object, utilize it else get from context
|
|
1621
|
+
let parent = typeof test.parent === 'object' ? test.parent : test.ctx.test.parent;
|
|
1622
|
+
while (parent && parent.title !== '') {
|
|
1623
|
+
value.push(parent.title);
|
|
1624
|
+
parent = parent.parent;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
else if (test.description && test.fullName) {
|
|
1628
|
+
// for Jasmine
|
|
1629
|
+
value.push(test.description);
|
|
1630
|
+
// Strip the trailing " <description>" suffix without building a regex from
|
|
1631
|
+
// a variable (avoids ReDoS and regex-metacharacter mismatches in descriptions).
|
|
1632
|
+
const descSuffix = ` ${test.description}`;
|
|
1633
|
+
value.push(test.fullName.endsWith(descSuffix) ? test.fullName.slice(0, -descSuffix.length) : test.fullName);
|
|
1634
|
+
}
|
|
1635
|
+
return value.reverse();
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Checks if the capabilities represent a multiremote configuration
|
|
1639
|
+
* @param capabilities - The capabilities to check
|
|
1640
|
+
* @returns true if capabilities represent any multiremote configuration (regular or parallel)
|
|
1641
|
+
*
|
|
1642
|
+
* @example
|
|
1643
|
+
* Regular multiremote (object):
|
|
1644
|
+
* { browserA: { capabilities: {...} }, browserB: { capabilities: {...} } }
|
|
1645
|
+
*
|
|
1646
|
+
* Parallel multiremote (array with nested structure):
|
|
1647
|
+
* [{ browserA: { capabilities: {...} }, browserB: { capabilities: {...} } }]
|
|
1648
|
+
*
|
|
1649
|
+
* Regular capabilities (array):
|
|
1650
|
+
* [{ browserName: 'chrome', ... }]
|
|
1651
|
+
*/
|
|
1652
|
+
export function isMultiRemoteCaps(capabilities) {
|
|
1653
|
+
// Regular multiremote is an object (not array)
|
|
1654
|
+
if (!Array.isArray(capabilities)) {
|
|
1655
|
+
return true;
|
|
1656
|
+
}
|
|
1657
|
+
// Empty array is not multiremote
|
|
1658
|
+
if (capabilities.length === 0) {
|
|
1659
|
+
return false;
|
|
1660
|
+
}
|
|
1661
|
+
// Parallel multiremote is an array with nested capabilities structure
|
|
1662
|
+
return capabilities.every(cap => Object.values(cap).length > 0 &&
|
|
1663
|
+
Object.values(cap).every(c => c !== null && typeof c === 'object' && c.capabilities));
|
|
1664
|
+
}
|
|
1665
|
+
//# sourceMappingURL=util.js.map
|