@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
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import got from 'got';
|
|
8
|
+
import { FormData } from 'formdata-node';
|
|
9
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { promisify, format } from 'node:util';
|
|
13
|
+
import { performance, PerformanceObserver } from 'node:perf_hooks';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import { SevereServiceError } from 'webdriverio';
|
|
16
|
+
import * as BrowserstackLocalLauncher from 'browserstack-local';
|
|
17
|
+
import { startPercy, stopPercy, getBestPlatformForPercySnapshot } from './Percy/PercyHelper.js';
|
|
18
|
+
import { BSTACK_SERVICE_VERSION, NOT_ALLOWED_KEYS_IN_CAPS, PERF_MEASUREMENT_ENV, RERUN_ENV, RERUN_TESTS_ENV, BROWSERSTACK_TESTHUB_UUID, VALID_APP_EXTENSION, BROWSERSTACK_PERCY, BROWSERSTACK_OBSERVABILITY, WDIO_NAMING_PREFIX } from './constants.js';
|
|
19
|
+
import { launchTestSession, shouldAddServiceVersion, stopBuildUpstream, getCiInfo, isBStackSession, isUndefined, isAccessibilityAutomationSession, isTrue, getBrowserStackUser, getBrowserStackKey, uploadLogs, ObjectsAreEqual, isValidCapsForHealing, getBooleanValueFromString, validateCapsWithNonBstackA11y, mergeChromeOptions, normalizeTestReportingConfig, normalizeTestReportingEnvVariables, isValidEnabledValue, isMultiRemoteCaps } from './util.js';
|
|
20
|
+
import { getProductMap } from './testHub/utils.js';
|
|
21
|
+
import CrashReporter from './crash-reporter.js';
|
|
22
|
+
import { BStackLogger } from './bstackLogger.js';
|
|
23
|
+
import { PercyLogger } from './Percy/PercyLogger.js';
|
|
24
|
+
import { FileStream } from './fileStream.js';
|
|
25
|
+
import { sendStart, sendFinish } from './instrumentation/funnelInstrumentation.js';
|
|
26
|
+
import BrowserStackConfig from './config.js';
|
|
27
|
+
import { setupExitHandlers } from './exitHandler.js';
|
|
28
|
+
import AiHandler from './ai-handler.js';
|
|
29
|
+
import TestOpsConfig from './testOps/testOpsConfig.js';
|
|
30
|
+
import PerformanceTester from './instrumentation/performance/performance-tester.js';
|
|
31
|
+
import * as PERFORMANCE_SDK_EVENTS from './instrumentation/performance/constants.js';
|
|
32
|
+
import { BrowserstackCLI } from './cli/index.js';
|
|
33
|
+
import { CLIUtils } from './cli/cliUtils.js';
|
|
34
|
+
import accessibilityScripts from './scripts/accessibility-scripts.js';
|
|
35
|
+
import util from 'node:util';
|
|
36
|
+
import APIUtils from './cli/apiUtils.js';
|
|
37
|
+
export default class BrowserstackLauncherService {
|
|
38
|
+
_options;
|
|
39
|
+
_config;
|
|
40
|
+
browserstackLocal;
|
|
41
|
+
_buildName;
|
|
42
|
+
_projectName;
|
|
43
|
+
_buildTag;
|
|
44
|
+
_buildIdentifier;
|
|
45
|
+
_accessibilityAutomation = null;
|
|
46
|
+
_percy;
|
|
47
|
+
_percyBestPlatformCaps;
|
|
48
|
+
browserStackConfig;
|
|
49
|
+
constructor(_options, capabilities, _config) {
|
|
50
|
+
this._options = _options;
|
|
51
|
+
this._config = _config;
|
|
52
|
+
BStackLogger.clearLogFile();
|
|
53
|
+
PercyLogger.clearLogFile();
|
|
54
|
+
setupExitHandlers();
|
|
55
|
+
// added to maintain backward compatibility with webdriverIO v5
|
|
56
|
+
this._config || (this._config = _options);
|
|
57
|
+
//normalizing testReporting config and env variables
|
|
58
|
+
normalizeTestReportingConfig(this._options);
|
|
59
|
+
normalizeTestReportingEnvVariables();
|
|
60
|
+
this.browserStackConfig = BrowserStackConfig.getInstance(_options, _config);
|
|
61
|
+
if (Array.isArray(capabilities)) {
|
|
62
|
+
capabilities
|
|
63
|
+
.flatMap((c) => {
|
|
64
|
+
if (Object.values(c).length > 0 && Object.values(c).every(c => typeof c === 'object' && c.capabilities)) {
|
|
65
|
+
return Object.values(c).map((o) => o.capabilities);
|
|
66
|
+
}
|
|
67
|
+
return c;
|
|
68
|
+
})
|
|
69
|
+
.forEach((capability) => {
|
|
70
|
+
if (!capability['bstack:options']) {
|
|
71
|
+
// Skipping adding of service version if session is not of browserstack
|
|
72
|
+
if (isBStackSession(this._config)) {
|
|
73
|
+
const extensionCaps = Object.keys(capability).filter((cap) => cap.includes(':'));
|
|
74
|
+
if (extensionCaps.length) {
|
|
75
|
+
capability['bstack:options'] = { wdioService: BSTACK_SERVICE_VERSION };
|
|
76
|
+
if (!isUndefined(capability['browserstack.accessibility'])) {
|
|
77
|
+
this._accessibilityAutomation ||= isTrue(capability['browserstack.accessibility']);
|
|
78
|
+
}
|
|
79
|
+
else if (isTrue(this._options.accessibility)) {
|
|
80
|
+
capability['bstack:options'].accessibility = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (shouldAddServiceVersion(this._config, this._options.testObservability)) {
|
|
84
|
+
capability['browserstack.wdioService'] = BSTACK_SERVICE_VERSION;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Need this details for sending data to Observability
|
|
88
|
+
this._buildIdentifier = capability['browserstack.buildIdentifier']?.toString();
|
|
89
|
+
this._buildName = capability.build?.toString();
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
capability['bstack:options'].wdioService = BSTACK_SERVICE_VERSION;
|
|
93
|
+
this._buildName = capability['bstack:options'].buildName;
|
|
94
|
+
this._projectName = capability['bstack:options'].projectName;
|
|
95
|
+
this._buildTag = capability['bstack:options'].buildTag;
|
|
96
|
+
this._buildIdentifier = capability['bstack:options'].buildIdentifier;
|
|
97
|
+
if (!isUndefined(capability['bstack:options'].accessibility)) {
|
|
98
|
+
this._accessibilityAutomation ||= isTrue(capability['bstack:options'].accessibility);
|
|
99
|
+
}
|
|
100
|
+
else if (isTrue(this._options.accessibility)) {
|
|
101
|
+
capability['bstack:options'].accessibility = (isTrue(this._options.accessibility));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else if (typeof capabilities === 'object') {
|
|
107
|
+
Object.entries(capabilities).forEach(([, caps]) => {
|
|
108
|
+
if (!caps.capabilities['bstack:options']) {
|
|
109
|
+
if (isBStackSession(this._config)) {
|
|
110
|
+
const extensionCaps = Object.keys(caps.capabilities).filter((cap) => cap.includes(':'));
|
|
111
|
+
if (extensionCaps.length) {
|
|
112
|
+
caps.capabilities['bstack:options'] = { wdioService: BSTACK_SERVICE_VERSION };
|
|
113
|
+
if (!isUndefined(caps.capabilities['browserstack.accessibility'])) {
|
|
114
|
+
this._accessibilityAutomation ||= isTrue(caps.capabilities['browserstack.accessibility']);
|
|
115
|
+
}
|
|
116
|
+
else if (isTrue(this._options.accessibility)) {
|
|
117
|
+
caps.capabilities['bstack:options'] = { wdioService: BSTACK_SERVICE_VERSION, accessibility: (isTrue(this._options.accessibility)) };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (shouldAddServiceVersion(this._config, this._options.testObservability)) {
|
|
121
|
+
caps.capabilities['browserstack.wdioService'] = BSTACK_SERVICE_VERSION;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
this._buildIdentifier = caps.capabilities['browserstack.buildIdentifier'];
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const bstackOptions = caps.capabilities['bstack:options'];
|
|
128
|
+
bstackOptions.wdioService = BSTACK_SERVICE_VERSION;
|
|
129
|
+
this._buildName = bstackOptions.buildName;
|
|
130
|
+
this._projectName = bstackOptions.projectName;
|
|
131
|
+
this._buildTag = bstackOptions.buildTag;
|
|
132
|
+
this._buildIdentifier = bstackOptions.buildIdentifier;
|
|
133
|
+
if (!isUndefined(bstackOptions.accessibility)) {
|
|
134
|
+
this._accessibilityAutomation ||= isTrue(bstackOptions.accessibility);
|
|
135
|
+
}
|
|
136
|
+
else if (isTrue(this._options.accessibility)) {
|
|
137
|
+
bstackOptions.accessibility = isTrue(this._options.accessibility);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
this.browserStackConfig.buildIdentifier = this._buildIdentifier;
|
|
143
|
+
this.browserStackConfig.buildName = this._buildName;
|
|
144
|
+
PerformanceTester.startMonitoring('performance-report-launcher.csv');
|
|
145
|
+
if (!isUndefined(this._options.accessibility)) {
|
|
146
|
+
this._accessibilityAutomation ||= isTrue(this._options.accessibility);
|
|
147
|
+
}
|
|
148
|
+
this._options.accessibility = this._accessibilityAutomation;
|
|
149
|
+
// by default observability will be true unless specified as false
|
|
150
|
+
this._options.testObservability = this._options.testObservability !== false;
|
|
151
|
+
if (this._options.testObservability
|
|
152
|
+
&&
|
|
153
|
+
// update files to run if it's a rerun
|
|
154
|
+
process.env[RERUN_ENV] && process.env[RERUN_TESTS_ENV]) {
|
|
155
|
+
this._config.specs = process.env[RERUN_TESTS_ENV].split(',');
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
CrashReporter.setConfigDetails(this._config, capabilities, this._options);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
BStackLogger.error(`[Crash_Report_Upload] Config processing failed due to ${error}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async onWorkerStart(cid, caps) {
|
|
165
|
+
try {
|
|
166
|
+
if (this._options.percy && this._percyBestPlatformCaps) {
|
|
167
|
+
const isThisBestPercyPlatform = ObjectsAreEqual(caps, this._percyBestPlatformCaps);
|
|
168
|
+
if (isThisBestPercyPlatform) {
|
|
169
|
+
process.env.BEST_PLATFORM_CID = cid;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
PercyLogger.error(`Error while setting best platform for Percy snapshot at worker start ${err}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async onPrepare(config, capabilities) {
|
|
178
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.INIT);
|
|
179
|
+
// // Send Funnel start request
|
|
180
|
+
await sendStart(this.browserStackConfig);
|
|
181
|
+
// Convert glob patterns in specs to resolved relative paths
|
|
182
|
+
if (config.specs && Array.isArray(config.specs) && isValidEnabledValue(this._options.testOrchestrationOptions?.runSmartSelection?.enabled)) {
|
|
183
|
+
try {
|
|
184
|
+
// Import glob for expanding file patterns
|
|
185
|
+
const glob = (await import('glob')).sync;
|
|
186
|
+
const path = await import('node:path');
|
|
187
|
+
// Use ConfigParser.getFilePaths equivalent logic to expand specs
|
|
188
|
+
const expandedSpecs = [];
|
|
189
|
+
for (const specPattern of config.specs) {
|
|
190
|
+
if (typeof specPattern === 'string') {
|
|
191
|
+
if (specPattern.startsWith('file://')) {
|
|
192
|
+
expandedSpecs.push(specPattern);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
// Expand glob pattern to relative paths
|
|
196
|
+
const pattern = specPattern.replace(/\\/g, '/');
|
|
197
|
+
// Use config.rootDir which is set to the config file's directory
|
|
198
|
+
const rootDir = config.rootDir || process.cwd();
|
|
199
|
+
// Get current working directory for final relative path calculation
|
|
200
|
+
const cwd = process.cwd();
|
|
201
|
+
const filenames = glob(pattern, {
|
|
202
|
+
cwd: rootDir,
|
|
203
|
+
matchBase: true
|
|
204
|
+
}) || [];
|
|
205
|
+
// Convert paths to be relative to the current working directory (where command is run)
|
|
206
|
+
filenames
|
|
207
|
+
.forEach((filename) => {
|
|
208
|
+
let absolutePath = filename;
|
|
209
|
+
// If filename is not absolute, resolve it relative to rootDir (config file's directory)
|
|
210
|
+
if (!path.isAbsolute(filename)) {
|
|
211
|
+
absolutePath = path.resolve(rootDir, filename);
|
|
212
|
+
}
|
|
213
|
+
// Make path relative to current working directory
|
|
214
|
+
let relativePath = path.relative(cwd, absolutePath);
|
|
215
|
+
// Normalize path separators for consistency (Windows compatibility)
|
|
216
|
+
relativePath = relativePath.replace(/\\/g, '/');
|
|
217
|
+
expandedSpecs.push(relativePath);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (expandedSpecs.length > 0) {
|
|
222
|
+
BStackLogger.info(`Expanded specs from glob patterns to ${expandedSpecs.length} files`);
|
|
223
|
+
config.specs = expandedSpecs;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
BStackLogger.error(`Failed to expand spec patterns: ${error}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
// Detect if multi-remote and disable CLI for those sessions
|
|
232
|
+
const isMultiremote = isMultiRemoteCaps(capabilities);
|
|
233
|
+
process.env.BROWSERSTACK_IS_MULTIREMOTE = String(isMultiremote);
|
|
234
|
+
if (CLIUtils.checkCLISupportedFrameworks(config.framework) && !isMultiremote) {
|
|
235
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.START);
|
|
236
|
+
CLIUtils.setFrameworkDetail(WDIO_NAMING_PREFIX + config.framework, 'WebdriverIO'); // TODO: make this constant
|
|
237
|
+
const binconfig = CLIUtils.getBinConfig(config, capabilities, this._options, this._buildTag);
|
|
238
|
+
await BrowserstackCLI.getInstance().bootstrap(this._options, config, binconfig);
|
|
239
|
+
BStackLogger.debug(`Is CLI running ${BrowserstackCLI.getInstance().isRunning()}`);
|
|
240
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.START);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
BStackLogger.error(`Error while starting CLI ${err}`);
|
|
245
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.START, false, util.format(err));
|
|
246
|
+
}
|
|
247
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.INIT);
|
|
248
|
+
// Setting up healing for those sessions where we don't add the service version capability as it indicates that the session is not being run on BrowserStack
|
|
249
|
+
if (!shouldAddServiceVersion(this._config, this._options.testObservability, capabilities)) {
|
|
250
|
+
try {
|
|
251
|
+
if (capabilities.browserName) {
|
|
252
|
+
capabilities = await AiHandler.setup(this._config, this.browserStackConfig, this._options, capabilities, false);
|
|
253
|
+
}
|
|
254
|
+
else if (Array.isArray(capabilities)) {
|
|
255
|
+
for (let i = 0; i < capabilities.length; i++) {
|
|
256
|
+
if (capabilities[i].browserName) {
|
|
257
|
+
capabilities[i] = await AiHandler.setup(this._config, this.browserStackConfig, this._options, capabilities[i], false);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else if (isValidCapsForHealing(capabilities)) {
|
|
262
|
+
// setting up healing in case capabilities.xyz.capabilities.browserName where xyz can be anything:
|
|
263
|
+
capabilities = await AiHandler.setup(this._config, this.browserStackConfig, this._options, capabilities, true);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
if (this._options.selfHeal === true) {
|
|
268
|
+
BStackLogger.warn(`Error while setting up Browserstack healing Extension ${err}. Disabling healing for this session.`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Upload app to BrowserStack if valid file path to app is given.
|
|
274
|
+
* Update app value of capability directly if app_url, custom_id, shareable_id is given
|
|
275
|
+
*/
|
|
276
|
+
if (!BrowserstackCLI.getInstance().isRunning()) {
|
|
277
|
+
if (!this._options.app) {
|
|
278
|
+
BStackLogger.debug('app is not defined in browserstack-service config, skipping ...');
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
let app = {};
|
|
282
|
+
const appConfig = this._options.app;
|
|
283
|
+
try {
|
|
284
|
+
app = await this._validateApp(appConfig);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
throw new SevereServiceError(error);
|
|
288
|
+
}
|
|
289
|
+
if (VALID_APP_EXTENSION.includes(path.extname(app.app))) {
|
|
290
|
+
if (fs.existsSync(app.app)) {
|
|
291
|
+
const data = await this._uploadApp(app);
|
|
292
|
+
BStackLogger.info(`app upload completed: ${JSON.stringify(data)}`);
|
|
293
|
+
app.app = data.app_url;
|
|
294
|
+
}
|
|
295
|
+
else if (app.customId) {
|
|
296
|
+
app.app = app.customId;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
throw new SevereServiceError(`[Invalid app path] app path ${app.app} is not correct, Provide correct path to app under test`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
BStackLogger.info(`Using app: ${app.app}`);
|
|
303
|
+
this._updateCaps(capabilities, 'app', app.app);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* buildIdentifier in service options will take precedence over specified in capabilities
|
|
308
|
+
*/
|
|
309
|
+
if (this._options.buildIdentifier) {
|
|
310
|
+
this._buildIdentifier = this._options.buildIdentifier;
|
|
311
|
+
this._updateCaps(capabilities, 'buildIdentifier', this._buildIdentifier);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* evaluate buildIdentifier in case unique execution identifiers are present
|
|
315
|
+
* e.g., ${BUILD_NUMBER} and ${DATE_TIME}
|
|
316
|
+
*/
|
|
317
|
+
this._handleBuildIdentifier(capabilities);
|
|
318
|
+
// remove accessibilityOptions from the capabilities if present
|
|
319
|
+
this._updateObjectTypeCaps(capabilities, 'accessibilityOptions');
|
|
320
|
+
const shouldSetupPercy = this._options.percy || (isUndefined(this._options.percy) && this._options.app);
|
|
321
|
+
let buildStartResponse = null;
|
|
322
|
+
if (!BrowserstackCLI.getInstance().isRunning() && (this._options.testObservability || this._accessibilityAutomation || shouldSetupPercy)) {
|
|
323
|
+
BStackLogger.debug('Sending launch start event');
|
|
324
|
+
buildStartResponse = await launchTestSession(this._options, this._config, {
|
|
325
|
+
projectName: this._projectName,
|
|
326
|
+
buildName: this._buildName,
|
|
327
|
+
buildTag: this._buildTag,
|
|
328
|
+
bstackServiceVersion: BSTACK_SERVICE_VERSION,
|
|
329
|
+
buildIdentifier: this._buildIdentifier
|
|
330
|
+
}, this.browserStackConfig, this._accessibilityAutomation);
|
|
331
|
+
//added checks for Accessibility running on non-bstack infra
|
|
332
|
+
if (isAccessibilityAutomationSession(this._accessibilityAutomation) && (process.env.BROWSERSTACK_TURBOSCALE || !shouldAddServiceVersion(this._config, this._options.testObservability))) {
|
|
333
|
+
const overrideOptions = accessibilityScripts.ChromeExtension;
|
|
334
|
+
this._updateObjectTypeCaps(capabilities, 'goog:chromeOptions', overrideOptions);
|
|
335
|
+
}
|
|
336
|
+
if (buildStartResponse?.accessibility) {
|
|
337
|
+
if (this._accessibilityAutomation === null) {
|
|
338
|
+
this.browserStackConfig.accessibility = buildStartResponse.accessibility.success;
|
|
339
|
+
this._accessibilityAutomation = buildStartResponse.accessibility.success;
|
|
340
|
+
this._options.accessibility = buildStartResponse.accessibility.success;
|
|
341
|
+
if (buildStartResponse.accessibility.success === true) {
|
|
342
|
+
this._updateCaps(capabilities, 'accessibility', 'true');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
this.browserStackConfig.accessibility = this._accessibilityAutomation;
|
|
347
|
+
if (this._accessibilityAutomation && this._options.accessibilityOptions) {
|
|
348
|
+
const filteredOpts = Object.keys(this._options.accessibilityOptions)
|
|
349
|
+
.filter(key => !NOT_ALLOWED_KEYS_IN_CAPS.includes(key))
|
|
350
|
+
.reduce((opts, key) => {
|
|
351
|
+
return {
|
|
352
|
+
...opts,
|
|
353
|
+
[key]: this._options.accessibilityOptions?.[key]
|
|
354
|
+
};
|
|
355
|
+
}, {});
|
|
356
|
+
this._updateObjectTypeCaps(capabilities, 'accessibilityOptions', filteredOpts);
|
|
357
|
+
}
|
|
358
|
+
else if (isAccessibilityAutomationSession(this._accessibilityAutomation)) {
|
|
359
|
+
this._updateObjectTypeCaps(capabilities, 'accessibilityOptions', {});
|
|
360
|
+
}
|
|
361
|
+
this._removeCliOnlyCapabilityOptions(capabilities);
|
|
362
|
+
if (shouldSetupPercy) {
|
|
363
|
+
try {
|
|
364
|
+
const bestPlatformPercyCaps = getBestPlatformForPercySnapshot(capabilities);
|
|
365
|
+
this._percyBestPlatformCaps = bestPlatformPercyCaps;
|
|
366
|
+
process.env[BROWSERSTACK_PERCY] = 'false';
|
|
367
|
+
await this.setupPercy(this._options, this._config, {
|
|
368
|
+
projectName: this._projectName
|
|
369
|
+
});
|
|
370
|
+
this._updateBrowserStackPercyConfig();
|
|
371
|
+
}
|
|
372
|
+
catch (err) {
|
|
373
|
+
PercyLogger.error(`Error while setting up Percy ${err}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// send testhub build uuid and product map instrumentation
|
|
378
|
+
this._updateCaps(capabilities, 'testhubBuildUuid');
|
|
379
|
+
this._updateCaps(capabilities, 'buildProductMap');
|
|
380
|
+
if (isValidEnabledValue(this._options.testOrchestrationOptions?.runSmartSelection?.enabled)) {
|
|
381
|
+
// Helper function to convert specs from cwd-relative to rootDir-relative
|
|
382
|
+
const convertToRootDirRelative = (specs) => {
|
|
383
|
+
const rootDir = config.rootDir || process.cwd();
|
|
384
|
+
const cwd = process.cwd();
|
|
385
|
+
return specs.map((spec) => {
|
|
386
|
+
if (typeof spec !== 'string') {
|
|
387
|
+
return spec;
|
|
388
|
+
}
|
|
389
|
+
// Convert from cwd-relative to absolute
|
|
390
|
+
const absolutePath = path.isAbsolute(spec) ? spec : path.resolve(cwd, spec);
|
|
391
|
+
// Then make it relative to rootDir (config file's directory)
|
|
392
|
+
const relativePath = path.relative(rootDir, absolutePath);
|
|
393
|
+
// Normalize path separators
|
|
394
|
+
return relativePath.replace(/\\/g, '/');
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
// Apply test orchestration if enabled
|
|
398
|
+
try {
|
|
399
|
+
// Import dynamically to avoid circular dependencies
|
|
400
|
+
const { applyOrchestrationIfEnabled } = await import('./testorchestration/apply-orchestration.js');
|
|
401
|
+
if (config.specs && config.specs.length > 0 && this._options.testObservability && isValidEnabledValue(this._options.testOrchestrationOptions?.runSmartSelection?.enabled)) {
|
|
402
|
+
BStackLogger.info('Applying test orchestration');
|
|
403
|
+
// Ensure we're passing string[] to applyOrchestrationIfEnabled
|
|
404
|
+
const specs = config.specs.filter(spec => typeof spec === 'string');
|
|
405
|
+
console.log(`Specs before orchestration: ${specs}`);
|
|
406
|
+
const orderedSpecs = await applyOrchestrationIfEnabled(specs, this._options);
|
|
407
|
+
console.log(`Specs after orchestration before conversion: ${orderedSpecs}`);
|
|
408
|
+
// Use ordered specs if available, otherwise use original specs
|
|
409
|
+
const specsToConvert = orderedSpecs && orderedSpecs.length > 0 ? orderedSpecs : specs;
|
|
410
|
+
config.specs = convertToRootDirRelative(specsToConvert);
|
|
411
|
+
console.log(`Specs after orchestration: ${config.specs}`);
|
|
412
|
+
BStackLogger.info('Test specs updated with orchestrated order');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
BStackLogger.error(`Error applying test orchestration: ${error}`);
|
|
417
|
+
// On error, we still need to convert specs from cwd-relative to rootDir-relative
|
|
418
|
+
if (config.specs && config.specs.length > 0) {
|
|
419
|
+
const specs = config.specs.filter(spec => typeof spec === 'string');
|
|
420
|
+
config.specs = convertToRootDirRelative(specs);
|
|
421
|
+
BStackLogger.debug(`Specs converted back to rootDir-relative after error: ${config.specs}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// local binary will be handled by CLI
|
|
426
|
+
if (BrowserstackCLI.getInstance().isRunning()) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (!this._options.browserstackLocal) {
|
|
430
|
+
return BStackLogger.info('browserstackLocal is not enabled - skipping...');
|
|
431
|
+
}
|
|
432
|
+
const opts = {
|
|
433
|
+
key: this._config.key,
|
|
434
|
+
...this._options.opts
|
|
435
|
+
};
|
|
436
|
+
this.browserstackLocal = new BrowserstackLocalLauncher.Local();
|
|
437
|
+
this._updateCaps(capabilities, 'local');
|
|
438
|
+
if (opts.localIdentifier) {
|
|
439
|
+
this._updateCaps(capabilities, 'localIdentifier', opts.localIdentifier);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* measure BrowserStack tunnel boot time
|
|
443
|
+
*/
|
|
444
|
+
const obs = new PerformanceObserver((list) => {
|
|
445
|
+
const entry = list.getEntries()[0];
|
|
446
|
+
BStackLogger.info(`Browserstack Local successfully started after ${entry.duration}ms`);
|
|
447
|
+
});
|
|
448
|
+
obs.observe({ entryTypes: ['measure'] });
|
|
449
|
+
let timer;
|
|
450
|
+
performance.mark('tbTunnelStart');
|
|
451
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.AUTOMATE_EVENTS.LOCAL_START);
|
|
452
|
+
return Promise.race([
|
|
453
|
+
promisify(this.browserstackLocal.start.bind(this.browserstackLocal))(opts),
|
|
454
|
+
new Promise((resolve, reject) => {
|
|
455
|
+
/* istanbul ignore next */
|
|
456
|
+
timer = setTimeout(function () {
|
|
457
|
+
reject('Browserstack Local failed to start within 60 seconds!');
|
|
458
|
+
}, 60000);
|
|
459
|
+
})
|
|
460
|
+
]).then(function (result) {
|
|
461
|
+
clearTimeout(timer);
|
|
462
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.AUTOMATE_EVENTS.LOCAL_START);
|
|
463
|
+
performance.mark('tbTunnelEnd');
|
|
464
|
+
performance.measure('bootTime', 'tbTunnelStart', 'tbTunnelEnd');
|
|
465
|
+
return Promise.resolve(result);
|
|
466
|
+
}, function (err) {
|
|
467
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.AUTOMATE_EVENTS.LOCAL_START, false, err);
|
|
468
|
+
clearTimeout(timer);
|
|
469
|
+
return Promise.reject(err);
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
async onComplete() {
|
|
473
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP);
|
|
474
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_ON_STOP);
|
|
475
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.STOP);
|
|
476
|
+
try {
|
|
477
|
+
BStackLogger.debug('Inside OnComplete hook..');
|
|
478
|
+
BStackLogger.debug('Sending stop launch event');
|
|
479
|
+
const isCLIEnabled = BrowserstackCLI.getInstance().isRunning();
|
|
480
|
+
try {
|
|
481
|
+
await (isCLIEnabled ? BrowserstackCLI.getInstance().stop() : stopBuildUpstream());
|
|
482
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.STOP);
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
BStackLogger.error(`Error while stopping CLI ${err}`);
|
|
486
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.STOP, false, util.format(err));
|
|
487
|
+
}
|
|
488
|
+
if (process.env[BROWSERSTACK_OBSERVABILITY] && process.env[BROWSERSTACK_TESTHUB_UUID]) {
|
|
489
|
+
console.log(`\nVisit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TESTHUB_UUID]} to view build report, insights, and many more debugging information all at one place!\n`);
|
|
490
|
+
}
|
|
491
|
+
this.browserStackConfig.testObservability.buildStopped = true;
|
|
492
|
+
if (process.env[PERF_MEASUREMENT_ENV]) {
|
|
493
|
+
PerformanceTester.calculateTimes(['launchTestSession', 'stopBuildUpstream']);
|
|
494
|
+
if (!process.env.START_TIME) {
|
|
495
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP);
|
|
496
|
+
await PerformanceTester.stopAndGenerate('performance-launcher.html');
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const duration = (new Date()).getTime() - (new Date(process.env.START_TIME)).getTime();
|
|
500
|
+
BStackLogger.info(`Total duration is ${duration / 1000} s`);
|
|
501
|
+
}
|
|
502
|
+
BStackLogger.info(`BrowserStack service run ended for id: ${this.browserStackConfig?.sdkRunID} testhub id: ${TestOpsConfig.getInstance()?.buildHashedId}`);
|
|
503
|
+
await sendFinish(this.browserStackConfig, isCLIEnabled);
|
|
504
|
+
try {
|
|
505
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_SEND_LOGS);
|
|
506
|
+
await this._uploadServiceLogs();
|
|
507
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_SEND_LOGS);
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
BStackLogger.debug(`Failed to upload BrowserStack WDIO Service logs ${error}`);
|
|
511
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_SEND_LOGS, false, util.format(error));
|
|
512
|
+
}
|
|
513
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_ON_STOP);
|
|
514
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP);
|
|
515
|
+
await PerformanceTester.stopAndGenerate('performance-launcher.html');
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_ON_STOP, false, util.format(error));
|
|
519
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP, false, util.format(error));
|
|
520
|
+
await PerformanceTester.stopAndGenerate('performance-launcher.html');
|
|
521
|
+
BStackLogger.error(`Error in onComplete hook: ${error}`);
|
|
522
|
+
}
|
|
523
|
+
BStackLogger.clearLogger();
|
|
524
|
+
if (this._options.percy) {
|
|
525
|
+
await this.stopPercy();
|
|
526
|
+
PercyLogger.clearLogger();
|
|
527
|
+
}
|
|
528
|
+
// local binary will be handled by CLI
|
|
529
|
+
if (BrowserstackCLI.getInstance().isRunning()) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (!this.browserstackLocal || !this.browserstackLocal.isRunning()) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (this._options.forcedStop) {
|
|
536
|
+
return process.kill(this.browserstackLocal.pid);
|
|
537
|
+
}
|
|
538
|
+
let timer;
|
|
539
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.AUTOMATE_EVENTS.LOCAL_STOP);
|
|
540
|
+
return Promise.race([
|
|
541
|
+
new Promise((resolve, reject) => {
|
|
542
|
+
this.browserstackLocal?.stop((err) => {
|
|
543
|
+
if (err) {
|
|
544
|
+
return reject(err);
|
|
545
|
+
}
|
|
546
|
+
resolve();
|
|
547
|
+
});
|
|
548
|
+
}),
|
|
549
|
+
new Promise((resolve, reject) => {
|
|
550
|
+
/* istanbul ignore next */
|
|
551
|
+
timer = setTimeout(() => reject(new Error('Browserstack Local failed to stop within 60 seconds!')), 60000);
|
|
552
|
+
})
|
|
553
|
+
]).then(function (result) {
|
|
554
|
+
clearTimeout(timer);
|
|
555
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.AUTOMATE_EVENTS.LOCAL_STOP);
|
|
556
|
+
return Promise.resolve(result);
|
|
557
|
+
}, function (err) {
|
|
558
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.AUTOMATE_EVENTS.LOCAL_STOP, false, err);
|
|
559
|
+
clearTimeout(timer);
|
|
560
|
+
return Promise.reject(err);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
async setupPercy(options, config, bsConfig) {
|
|
564
|
+
if (this._percy?.isRunning()) {
|
|
565
|
+
process.env[BROWSERSTACK_PERCY] = 'true';
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
this._percy = await startPercy(options, config, bsConfig);
|
|
570
|
+
if (!this._percy) {
|
|
571
|
+
throw new Error('Could not start percy, check percy logs for info.');
|
|
572
|
+
}
|
|
573
|
+
PercyLogger.info('Percy started successfully');
|
|
574
|
+
process.env[BROWSERSTACK_PERCY] = 'true';
|
|
575
|
+
let signal = 0;
|
|
576
|
+
const handler = async () => {
|
|
577
|
+
signal++;
|
|
578
|
+
signal === 1 && await this.stopPercy();
|
|
579
|
+
};
|
|
580
|
+
process.on('beforeExit', handler);
|
|
581
|
+
process.on('SIGINT', handler);
|
|
582
|
+
process.on('SIGTERM', handler);
|
|
583
|
+
}
|
|
584
|
+
catch (err) {
|
|
585
|
+
PercyLogger.debug(`Error in percy setup ${err}`);
|
|
586
|
+
process.env[BROWSERSTACK_PERCY] = 'false';
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
async stopPercy() {
|
|
590
|
+
if (!this._percy || !this._percy.isRunning()) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
await stopPercy(this._percy);
|
|
595
|
+
PercyLogger.info('Percy stopped');
|
|
596
|
+
}
|
|
597
|
+
catch (err) {
|
|
598
|
+
PercyLogger.error('Error occured while stopping percy : ' + err);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async _uploadApp(app) {
|
|
602
|
+
BStackLogger.info(`uploading app ${app.app} ${app.customId ? `and custom_id: ${app.customId}` : ''} to browserstack`);
|
|
603
|
+
const form = new FormData();
|
|
604
|
+
if (app.app) {
|
|
605
|
+
const fileName = path.basename(app.app);
|
|
606
|
+
form.append('file', new FileStream(fs.createReadStream(app.app)), fileName);
|
|
607
|
+
}
|
|
608
|
+
if (app.customId) {
|
|
609
|
+
form.append('custom_id', app.customId);
|
|
610
|
+
}
|
|
611
|
+
const res = await got.post(`${APIUtils.BROWSERSTACK_AA_API_CLOUD_URL}/app-automate/upload`, {
|
|
612
|
+
body: form,
|
|
613
|
+
username: this._config.user,
|
|
614
|
+
password: this._config.key
|
|
615
|
+
}).json().catch((err) => {
|
|
616
|
+
throw new SevereServiceError(`app upload failed ${err.message}`);
|
|
617
|
+
});
|
|
618
|
+
return res;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* @param {String | AppConfig} appConfig <string>: should be "app file path" or "app_url" or "custom_id" or "shareable_id".
|
|
622
|
+
* <object>: only "path" and "custom_id" should coexist as multiple properties.
|
|
623
|
+
*/
|
|
624
|
+
async _validateApp(appConfig) {
|
|
625
|
+
const app = {};
|
|
626
|
+
if (typeof appConfig === 'string') {
|
|
627
|
+
app.app = appConfig;
|
|
628
|
+
}
|
|
629
|
+
else if (typeof appConfig === 'object' && Object.keys(appConfig).length) {
|
|
630
|
+
if (Object.keys(appConfig).length > 2 || (Object.keys(appConfig).length === 2 && (!appConfig.path || !appConfig.custom_id))) {
|
|
631
|
+
throw new SevereServiceError(`keys ${Object.keys(appConfig)} can't co-exist as app values, use any one property from
|
|
632
|
+
{id<string>, path<string>, custom_id<string>, shareable_id<string>}, only "path" and "custom_id" can co-exist.`);
|
|
633
|
+
}
|
|
634
|
+
app.app = appConfig.id || appConfig.path || appConfig.custom_id || appConfig.shareable_id;
|
|
635
|
+
app.customId = appConfig.custom_id;
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
throw new SevereServiceError('[Invalid format] app should be string or an object');
|
|
639
|
+
}
|
|
640
|
+
if (!app.app) {
|
|
641
|
+
throw new SevereServiceError(`[Invalid app property] supported properties are {id<string>, path<string>, custom_id<string>, shareable_id<string>}.
|
|
642
|
+
For more details please visit https://www.browserstack.com/docs/app-automate/appium/set-up-tests/specify-app ')`);
|
|
643
|
+
}
|
|
644
|
+
return app;
|
|
645
|
+
}
|
|
646
|
+
async _uploadServiceLogs() {
|
|
647
|
+
const clientBuildUuid = this._getClientBuildUuid();
|
|
648
|
+
await PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_UPLOAD_LOGS, async () => {
|
|
649
|
+
const response = await uploadLogs(getBrowserStackUser(this._config), getBrowserStackKey(this._config), clientBuildUuid);
|
|
650
|
+
BStackLogger.logToFile(`Response - ${format(response)}`, 'debug');
|
|
651
|
+
})();
|
|
652
|
+
}
|
|
653
|
+
_removeCliOnlyCapabilityOptions(capabilities) {
|
|
654
|
+
if (!capabilities || typeof capabilities !== 'object') {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const strip = (capability) => {
|
|
658
|
+
const capabilityRecord = capability;
|
|
659
|
+
const bstackOptions = capabilityRecord['bstack:options'];
|
|
660
|
+
if (bstackOptions && typeof bstackOptions === 'object') {
|
|
661
|
+
NOT_ALLOWED_KEYS_IN_CAPS.forEach(key => delete bstackOptions[key]);
|
|
662
|
+
}
|
|
663
|
+
NOT_ALLOWED_KEYS_IN_CAPS.forEach(key => delete capabilityRecord[`browserstack.${key}`]);
|
|
664
|
+
const alwaysMatch = capability.alwaysMatch;
|
|
665
|
+
if (alwaysMatch && typeof alwaysMatch === 'object') {
|
|
666
|
+
strip(alwaysMatch);
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
if (Array.isArray(capabilities)) {
|
|
670
|
+
capabilities
|
|
671
|
+
.flatMap((c) => {
|
|
672
|
+
if (Object.values(c).length > 0 && Object.values(c).every(c => typeof c === 'object' && c.capabilities)) {
|
|
673
|
+
return Object.values(c).map((o) => o.capabilities);
|
|
674
|
+
}
|
|
675
|
+
return c;
|
|
676
|
+
})
|
|
677
|
+
.forEach(strip);
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
Object.entries(capabilities).forEach(([, caps]) => {
|
|
681
|
+
strip(caps.capabilities);
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
_updateObjectTypeCaps(capabilities, capType, value) {
|
|
686
|
+
try {
|
|
687
|
+
if (Array.isArray(capabilities)) {
|
|
688
|
+
capabilities
|
|
689
|
+
.flatMap((c) => {
|
|
690
|
+
if (Object.values(c).length > 0 && Object.values(c).every(c => typeof c === 'object' && c.capabilities)) {
|
|
691
|
+
return Object.values(c).map((o) => o.capabilities);
|
|
692
|
+
}
|
|
693
|
+
return c;
|
|
694
|
+
})
|
|
695
|
+
.forEach((capability) => {
|
|
696
|
+
if (validateCapsWithNonBstackA11y(capability.browserName, capability.browserVersion)) {
|
|
697
|
+
if (capType === 'goog:chromeOptions' && value) {
|
|
698
|
+
const chromeOptions = capability['goog:chromeOptions'];
|
|
699
|
+
if (chromeOptions) {
|
|
700
|
+
const finalChromeOptions = mergeChromeOptions(chromeOptions, value);
|
|
701
|
+
capability['goog:chromeOptions'] = finalChromeOptions;
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
capability['goog:chromeOptions'] = value;
|
|
705
|
+
}
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (!capability['bstack:options']) {
|
|
710
|
+
const extensionCaps = Object.keys(capability).filter((cap) => cap.includes(':'));
|
|
711
|
+
if (extensionCaps.length) {
|
|
712
|
+
if (capType === 'accessibilityOptions' && value) {
|
|
713
|
+
capability['bstack:options'] = { accessibilityOptions: value };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
else if (capType === 'accessibilityOptions') {
|
|
717
|
+
if (value) {
|
|
718
|
+
const accessibilityOpts = { ...value };
|
|
719
|
+
if (capability?.accessibility) {
|
|
720
|
+
accessibilityOpts.authToken = process.env.BSTACK_A11Y_JWT;
|
|
721
|
+
accessibilityOpts.scannerVersion = process.env.BSTACK_A11Y_SCANNER_VERSION;
|
|
722
|
+
}
|
|
723
|
+
capability['browserstack.accessibilityOptions'] = accessibilityOpts;
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
delete capability['browserstack.accessibilityOptions'];
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
else if (capType === 'accessibilityOptions') {
|
|
731
|
+
if (value) {
|
|
732
|
+
const accessibilityOpts = { ...value };
|
|
733
|
+
if (capability['bstack:options'].accessibility) {
|
|
734
|
+
accessibilityOpts.authToken = process.env.BSTACK_A11Y_JWT;
|
|
735
|
+
accessibilityOpts.scannerVersion = process.env.BSTACK_A11Y_SCANNER_VERSION;
|
|
736
|
+
}
|
|
737
|
+
capability['bstack:options'].accessibilityOptions = accessibilityOpts;
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
delete capability['bstack:options'].accessibilityOptions;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
else if (typeof capabilities === 'object') {
|
|
746
|
+
Object.entries(capabilities).forEach(([, caps]) => {
|
|
747
|
+
if (validateCapsWithNonBstackA11y(caps.capabilities.browserName, caps.capabilities.browserVersion)) {
|
|
748
|
+
if (capType === 'goog:chromeOptions' && value) {
|
|
749
|
+
const chromeOptions = caps.capabilities['goog:chromeOptions'];
|
|
750
|
+
if (chromeOptions) {
|
|
751
|
+
const finalChromeOptions = mergeChromeOptions(chromeOptions, value);
|
|
752
|
+
caps.capabilities['goog:chromeOptions'] = finalChromeOptions;
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
caps.capabilities['goog:chromeOptions'] = value;
|
|
756
|
+
}
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (!caps.capabilities['bstack:options']) {
|
|
761
|
+
const extensionCaps = Object.keys(caps.capabilities).filter((cap) => cap.includes(':'));
|
|
762
|
+
if (extensionCaps.length) {
|
|
763
|
+
if (capType === 'accessibilityOptions' && value) {
|
|
764
|
+
caps.capabilities['bstack:options'] = { accessibilityOptions: value };
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
else if (capType === 'accessibilityOptions') {
|
|
768
|
+
if (value) {
|
|
769
|
+
const accessibilityOpts = { ...value };
|
|
770
|
+
if (caps.capabilities['browserstack.accessibility']) {
|
|
771
|
+
accessibilityOpts.authToken = process.env.BSTACK_A11Y_JWT;
|
|
772
|
+
accessibilityOpts.scannerVersion = process.env.BSTACK_A11Y_SCANNER_VERSION;
|
|
773
|
+
}
|
|
774
|
+
caps.capabilities['browserstack.accessibilityOptions'] = accessibilityOpts;
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
delete caps.capabilities['browserstack.accessibilityOptions'];
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
else if (capType === 'accessibilityOptions') {
|
|
782
|
+
if (value) {
|
|
783
|
+
const accessibilityOpts = { ...value };
|
|
784
|
+
if (caps.capabilities['bstack:options'].accessibility) {
|
|
785
|
+
accessibilityOpts.authToken = process.env.BSTACK_A11Y_JWT;
|
|
786
|
+
accessibilityOpts.scannerVersion = process.env.BSTACK_A11Y_SCANNER_VERSION;
|
|
787
|
+
}
|
|
788
|
+
caps.capabilities['bstack:options'].accessibilityOptions = accessibilityOpts;
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
delete caps.capabilities['bstack:options'].accessibilityOptions;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
catch (error) {
|
|
798
|
+
BStackLogger.debug(`Exception while retrieving capability value. Error - ${error}`);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
_updateCaps(capabilities, capType, value) {
|
|
802
|
+
if (Array.isArray(capabilities)) {
|
|
803
|
+
capabilities
|
|
804
|
+
.flatMap((c) => {
|
|
805
|
+
if (Object.values(c).length > 0 && Object.values(c).every(c => typeof c === 'object' && c.capabilities)) {
|
|
806
|
+
return Object.values(c).map((o) => o.capabilities);
|
|
807
|
+
}
|
|
808
|
+
return c;
|
|
809
|
+
})
|
|
810
|
+
.forEach((capability) => {
|
|
811
|
+
if (!capability['bstack:options']) {
|
|
812
|
+
const extensionCaps = Object.keys(capability).filter((cap) => cap.includes(':'));
|
|
813
|
+
if (extensionCaps.length) {
|
|
814
|
+
if (capType === 'local') {
|
|
815
|
+
capability['bstack:options'] = { local: true };
|
|
816
|
+
}
|
|
817
|
+
else if (capType === 'app') {
|
|
818
|
+
capability['appium:app'] = value;
|
|
819
|
+
}
|
|
820
|
+
else if (capType === 'buildIdentifier' && value) {
|
|
821
|
+
capability['bstack:options'] = { buildIdentifier: value };
|
|
822
|
+
}
|
|
823
|
+
else if (capType === 'testhubBuildUuid' && TestOpsConfig.getInstance().buildHashedId) {
|
|
824
|
+
capability['bstack:options'] = { testhubBuildUuid: TestOpsConfig.getInstance().buildHashedId };
|
|
825
|
+
}
|
|
826
|
+
else if (capType === 'buildProductMap' && getProductMap(this.browserStackConfig)) {
|
|
827
|
+
capability['bstack:options'] = { buildProductMap: getProductMap(this.browserStackConfig) };
|
|
828
|
+
}
|
|
829
|
+
else if (capType === 'accessibility') {
|
|
830
|
+
capability['bstack:options'] = { accessibility: getBooleanValueFromString(value) };
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
else if (capType === 'local') {
|
|
834
|
+
capability['browserstack.local'] = true;
|
|
835
|
+
}
|
|
836
|
+
else if (capType === 'app') {
|
|
837
|
+
capability.app = value;
|
|
838
|
+
}
|
|
839
|
+
else if (capType === 'buildIdentifier') {
|
|
840
|
+
if (value) {
|
|
841
|
+
capability['browserstack.buildIdentifier'] = value;
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
delete capability['browserstack.buildIdentifier'];
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
else if (capType === 'localIdentifier') {
|
|
848
|
+
capability['browserstack.localIdentifier'] = value;
|
|
849
|
+
}
|
|
850
|
+
else if (capType === 'testhubBuildUuid') {
|
|
851
|
+
capability['browserstack.testhubBuildUuid'] = TestOpsConfig.getInstance().buildHashedId;
|
|
852
|
+
}
|
|
853
|
+
else if (capType === 'buildProductMap') {
|
|
854
|
+
capability['browserstack.buildProductMap'] = getProductMap(this.browserStackConfig);
|
|
855
|
+
}
|
|
856
|
+
else if (capType === 'accessibility') {
|
|
857
|
+
capability['browserstack.accessibility'] = getBooleanValueFromString(value);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
else if (capType === 'local') {
|
|
861
|
+
capability['bstack:options'].local = true;
|
|
862
|
+
}
|
|
863
|
+
else if (capType === 'app') {
|
|
864
|
+
capability['appium:app'] = value;
|
|
865
|
+
}
|
|
866
|
+
else if (capType === 'buildIdentifier') {
|
|
867
|
+
if (value) {
|
|
868
|
+
capability['bstack:options'].buildIdentifier = value;
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
delete capability['bstack:options'].buildIdentifier;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
else if (capType === 'localIdentifier') {
|
|
875
|
+
capability['bstack:options'].localIdentifier = value;
|
|
876
|
+
}
|
|
877
|
+
else if (capType === 'testhubBuildUuid') {
|
|
878
|
+
capability['bstack:options'].testhubBuildUuid = TestOpsConfig.getInstance().buildHashedId;
|
|
879
|
+
}
|
|
880
|
+
else if (capType === 'buildProductMap') {
|
|
881
|
+
capability['bstack:options'].buildProductMap = getProductMap(this.browserStackConfig);
|
|
882
|
+
}
|
|
883
|
+
else if (capType === 'accessibility') {
|
|
884
|
+
capability['bstack:options'].accessibility = getBooleanValueFromString(value);
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
else if (typeof capabilities === 'object') {
|
|
889
|
+
Object.entries(capabilities).forEach(([, caps]) => {
|
|
890
|
+
if (!caps.capabilities['bstack:options']) {
|
|
891
|
+
const extensionCaps = Object.keys(caps.capabilities).filter((cap) => cap.includes(':'));
|
|
892
|
+
if (extensionCaps.length) {
|
|
893
|
+
if (capType === 'local') {
|
|
894
|
+
caps.capabilities['bstack:options'] = { local: true };
|
|
895
|
+
}
|
|
896
|
+
else if (capType === 'app') {
|
|
897
|
+
caps.capabilities['appium:app'] = value;
|
|
898
|
+
}
|
|
899
|
+
else if (capType === 'buildIdentifier' && value) {
|
|
900
|
+
caps.capabilities['bstack:options'] = { buildIdentifier: value };
|
|
901
|
+
}
|
|
902
|
+
else if (capType === 'testhubBuildUuid') {
|
|
903
|
+
caps.capabilities['bstack:options'] = { testhubBuildUuid: TestOpsConfig.getInstance().buildHashedId };
|
|
904
|
+
}
|
|
905
|
+
else if (capType === 'buildProductMap') {
|
|
906
|
+
caps.capabilities['bstack:options'] = { buildProductMap: getProductMap(this.browserStackConfig) };
|
|
907
|
+
}
|
|
908
|
+
else if (capType === 'accessibility') {
|
|
909
|
+
caps.capabilities['bstack:options'] = { accessibility: getBooleanValueFromString(value) };
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
else if (capType === 'local') {
|
|
913
|
+
caps.capabilities['browserstack.local'] = true;
|
|
914
|
+
}
|
|
915
|
+
else if (capType === 'app') {
|
|
916
|
+
caps.capabilities['appium:app'] = value;
|
|
917
|
+
}
|
|
918
|
+
else if (capType === 'buildIdentifier') {
|
|
919
|
+
if (value) {
|
|
920
|
+
caps.capabilities['browserstack.buildIdentifier'] = value;
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
delete caps.capabilities['browserstack.buildIdentifier'];
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
else if (capType === 'localIdentifier') {
|
|
927
|
+
caps.capabilities['browserstack.localIdentifier'] = value;
|
|
928
|
+
}
|
|
929
|
+
else if (capType === 'testhubBuildUuid') {
|
|
930
|
+
caps.capabilities['browserstack.testhubBuildUuid'] = TestOpsConfig.getInstance().buildHashedId;
|
|
931
|
+
}
|
|
932
|
+
else if (capType === 'buildProductMap') {
|
|
933
|
+
caps.capabilities['browserstack.buildProductMap'] = getProductMap(this.browserStackConfig);
|
|
934
|
+
}
|
|
935
|
+
else if (capType === 'accessibility') {
|
|
936
|
+
caps.capabilities['browserstack.accessibility'] = getBooleanValueFromString(value);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
else if (capType === 'local') {
|
|
940
|
+
caps.capabilities['bstack:options'].local = true;
|
|
941
|
+
}
|
|
942
|
+
else if (capType === 'app') {
|
|
943
|
+
caps.capabilities['appium:app'] = value;
|
|
944
|
+
}
|
|
945
|
+
else if (capType === 'buildIdentifier') {
|
|
946
|
+
if (value) {
|
|
947
|
+
caps.capabilities['bstack:options'].buildIdentifier = value;
|
|
948
|
+
}
|
|
949
|
+
else {
|
|
950
|
+
delete caps.capabilities['bstack:options'].buildIdentifier;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
else if (capType === 'localIdentifier') {
|
|
954
|
+
caps.capabilities['bstack:options'].localIdentifier = value;
|
|
955
|
+
}
|
|
956
|
+
else if (capType === 'testhubBuildUuid') {
|
|
957
|
+
caps.capabilities['bstack:options'].testhubBuildUuid = TestOpsConfig.getInstance().buildHashedId;
|
|
958
|
+
}
|
|
959
|
+
else if (capType === 'buildProductMap') {
|
|
960
|
+
caps.capabilities['bstack:options'].buildProductMap = getProductMap(this.browserStackConfig);
|
|
961
|
+
}
|
|
962
|
+
else if (capType === 'accessibility') {
|
|
963
|
+
caps.capabilities['bstack:options'].accessibility = getBooleanValueFromString(value);
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
throw new SevereServiceError('Capabilities should be an object or Array!');
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
_updateBrowserStackPercyConfig() {
|
|
972
|
+
const { percyAutoEnabled = false, percyCaptureMode, buildId, percy } = this._percy || {};
|
|
973
|
+
// Setting to browserStackConfig for populating data in funnel instrumentaion
|
|
974
|
+
this.browserStackConfig.percyCaptureMode = percyCaptureMode;
|
|
975
|
+
this.browserStackConfig.percyBuildId = buildId;
|
|
976
|
+
this.browserStackConfig.isPercyAutoEnabled = percyAutoEnabled;
|
|
977
|
+
// To handle stop percy build
|
|
978
|
+
this._options.percy = percy;
|
|
979
|
+
// To pass data to workers
|
|
980
|
+
process.env.BROWSERSTACK_PERCY = String(percy);
|
|
981
|
+
process.env.BROWSERSTACK_PERCY_CAPTURE_MODE = percyCaptureMode;
|
|
982
|
+
}
|
|
983
|
+
_handleBuildIdentifier(capabilities) {
|
|
984
|
+
if (!this._buildIdentifier) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if ((!this._buildName || process.env.BROWSERSTACK_BUILD_NAME) && this._buildIdentifier) {
|
|
988
|
+
this._updateCaps(capabilities, 'buildIdentifier');
|
|
989
|
+
BStackLogger.warn('Skipping buildIdentifier as buildName is not passed.');
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (this._buildIdentifier && this._buildIdentifier.includes('${DATE_TIME}')) {
|
|
993
|
+
const formattedDate = new Intl.DateTimeFormat('en-GB', {
|
|
994
|
+
month: 'short',
|
|
995
|
+
day: '2-digit',
|
|
996
|
+
hour: '2-digit',
|
|
997
|
+
minute: '2-digit',
|
|
998
|
+
hour12: false
|
|
999
|
+
})
|
|
1000
|
+
.format(new Date())
|
|
1001
|
+
.replace(/ |, /g, '-');
|
|
1002
|
+
this._buildIdentifier = this._buildIdentifier.replace('${DATE_TIME}', formattedDate);
|
|
1003
|
+
this._updateCaps(capabilities, 'buildIdentifier', this._buildIdentifier);
|
|
1004
|
+
}
|
|
1005
|
+
if (!this._buildIdentifier.includes('${BUILD_NUMBER}')) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
const ciInfo = getCiInfo();
|
|
1009
|
+
if (ciInfo !== null && ciInfo.build_number) {
|
|
1010
|
+
this._buildIdentifier = this._buildIdentifier.replace('${BUILD_NUMBER}', 'CI ' + ciInfo.build_number);
|
|
1011
|
+
this._updateCaps(capabilities, 'buildIdentifier', this._buildIdentifier);
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
const localBuildNumber = this._getLocalBuildNumber();
|
|
1015
|
+
if (localBuildNumber) {
|
|
1016
|
+
this._buildIdentifier = this._buildIdentifier.replace('${BUILD_NUMBER}', localBuildNumber);
|
|
1017
|
+
this._updateCaps(capabilities, 'buildIdentifier', this._buildIdentifier);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* @return {string} if buildName doesn't exist in json file, it will return 1
|
|
1023
|
+
* else returns corresponding value in json file (e.g. { "wdio-build": { "identifier" : 2 } } => 2 in this case)
|
|
1024
|
+
*/
|
|
1025
|
+
_getLocalBuildNumber() {
|
|
1026
|
+
const browserstackFolderPath = path.join(os.homedir(), '.browserstack');
|
|
1027
|
+
try {
|
|
1028
|
+
if (!fs.existsSync(browserstackFolderPath)) {
|
|
1029
|
+
fs.mkdirSync(browserstackFolderPath);
|
|
1030
|
+
}
|
|
1031
|
+
const filePath = path.join(browserstackFolderPath, '.build-name-cache.json');
|
|
1032
|
+
if (!fs.existsSync(filePath)) {
|
|
1033
|
+
fs.appendFileSync(filePath, JSON.stringify({}));
|
|
1034
|
+
}
|
|
1035
|
+
const buildCacheFileData = fs.readFileSync(filePath);
|
|
1036
|
+
const parsedBuildCacheFileData = JSON.parse(buildCacheFileData.toString());
|
|
1037
|
+
if (this._buildName && this._buildName in parsedBuildCacheFileData) {
|
|
1038
|
+
const prevIdentifier = parseInt((parsedBuildCacheFileData[this._buildName].identifier));
|
|
1039
|
+
const newIdentifier = prevIdentifier + 1;
|
|
1040
|
+
this._updateLocalBuildCache(filePath, this._buildName, newIdentifier);
|
|
1041
|
+
return newIdentifier.toString();
|
|
1042
|
+
}
|
|
1043
|
+
const newIdentifier = 1;
|
|
1044
|
+
this._updateLocalBuildCache(filePath, this._buildName, 1);
|
|
1045
|
+
return newIdentifier.toString();
|
|
1046
|
+
}
|
|
1047
|
+
catch (error) {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
_updateLocalBuildCache(filePath, buildName, buildIdentifier) {
|
|
1052
|
+
if (!buildName || !filePath) {
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const jsonContent = JSON.parse(fs.readFileSync(filePath).toString());
|
|
1056
|
+
jsonContent[buildName] = { 'identifier': buildIdentifier };
|
|
1057
|
+
fs.writeFileSync(filePath, JSON.stringify(jsonContent));
|
|
1058
|
+
}
|
|
1059
|
+
_getClientBuildUuid() {
|
|
1060
|
+
if (process.env[BROWSERSTACK_TESTHUB_UUID]) {
|
|
1061
|
+
return process.env[BROWSERSTACK_TESTHUB_UUID];
|
|
1062
|
+
}
|
|
1063
|
+
const uuid = uuidv4();
|
|
1064
|
+
BStackLogger.logToFile(`If facing any issues, please contact BrowserStack support with the Build Run Id - ${uuid}`, 'info');
|
|
1065
|
+
return uuid;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
__decorate([
|
|
1069
|
+
PerformanceTester.Measure(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_SETUP)
|
|
1070
|
+
], BrowserstackLauncherService.prototype, "onWorkerStart", null);
|
|
1071
|
+
__decorate([
|
|
1072
|
+
PerformanceTester.Measure(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_PRE_TEST)
|
|
1073
|
+
], BrowserstackLauncherService.prototype, "onPrepare", null);
|
|
1074
|
+
__decorate([
|
|
1075
|
+
PerformanceTester.Measure(PERFORMANCE_SDK_EVENTS.APP_AUTOMATE_EVENTS.APP_UPLOAD)
|
|
1076
|
+
], BrowserstackLauncherService.prototype, "_uploadApp", null);
|
|
1077
|
+
//# sourceMappingURL=launcher.js.map
|