From f27849f0498530886ff372daba43af7e863f3856 Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Mon, 16 Jan 2023 23:58:38 +0530 Subject: [PATCH 1/9] Add support to upload app to app-automate. --- lib/http/http.js | 3 +- lib/http/request.js | 42 +++++++++++- .../browserstack/appAutomate.js | 66 ++++++++++++++++++- .../browserstack/browserstack.js | 8 +++ 4 files changed, 115 insertions(+), 4 deletions(-) diff --git a/lib/http/http.js b/lib/http/http.js index 799b461cdf..2c5f5caf62 100644 --- a/lib/http/http.js +++ b/lib/http/http.js @@ -1,6 +1,7 @@ const ContentTypes = { JSON: 'application/json', - JSON_WITH_CHARSET: 'application/json; charset=utf-8' + JSON_WITH_CHARSET: 'application/json; charset=utf-8', + MULTIPART_FORM_DATA: 'multipart/form-data' }; const Headers = { diff --git a/lib/http/request.js b/lib/http/request.js index 496f3ffdaf..c486f35515 100644 --- a/lib/http/request.js +++ b/lib/http/request.js @@ -4,6 +4,8 @@ const https = require('https'); const dns = require('dns'); const HttpUtil = require('./http.js'); const HttpOptions = require('./options.js'); +const fs = require('fs'); +const path = require('path'); const Auth = require('./auth.js'); const Formatter = require('./formatter.js'); const HttpResponse = require('./response.js'); @@ -88,8 +90,15 @@ class HttpRequest extends EventEmitter { options.data = ''; } + if (options.multiPartFormData) { + this.multiPartFormData = options.multiPartFormData; + this.formBoundary = `----NightwatchFormBoundary${Math.random().toString(16).slice(2)}`; + + this.setFormData(this.formBoundary); + } else { + this.setData(options); + } this.params = options.data; - this.setData(options); this.contentLength = this.data.length; this.use_ssl = this.httpOpts.use_ssl || options.use_ssl; this.reqOptions = this.createHttpOptions(options); @@ -364,7 +373,9 @@ class HttpRequest extends EventEmitter { this.reqOptions.headers[HttpUtil.Headers.ACCEPT] = HttpUtil.ContentTypes.JSON; } - if (this.contentLength > 0) { + if (this.multiPartFormData) { + this.reqOptions.headers[HttpUtil.Headers.CONTENT_TYPE] = `${HttpUtil.ContentTypes.MULTIPART_FORM_DATA}; boundary=${this.formBoundary}`; + } else if (this.contentLength > 0) { this.reqOptions.headers[HttpUtil.Headers.CONTENT_TYPE] = HttpUtil.ContentTypes.JSON_WITH_CHARSET; } @@ -391,6 +402,33 @@ class HttpRequest extends EventEmitter { return this; } + setFormData(boundary) { + const crlf = '\r\n'; + const delimiter = `${crlf}--${boundary}`; + const closeDelimiter = `${delimiter}--`; + + const bufferArray = []; + for (const [fieldName, fieldValue] of Object.entries(this.multiPartFormData)) { + // set header + let header = `Content-Disposition: form-data; name="${fieldName}"`; + if (fieldValue.filePath) { + const fileName = path.basename(fieldValue.filePath); + header += `; filename="${fileName}"`; + } + bufferArray.push(Buffer.from(delimiter + crlf + header + crlf + crlf)); + + // set data + if (fieldValue.filePath) { + bufferArray.push(fs.readFileSync(fieldValue.filePath)); + } else { + bufferArray.push(Buffer.from(fieldValue.data)); + } + } + bufferArray.push(Buffer.from(closeDelimiter)); + + this.data = Buffer.concat(bufferArray); + } + hasCredentials() { return Utils.isObject(this.httpOpts.credentials) && this.httpOpts.credentials.username; } diff --git a/lib/transport/selenium-webdriver/browserstack/appAutomate.js b/lib/transport/selenium-webdriver/browserstack/appAutomate.js index 79563ae3a2..f405a51db1 100644 --- a/lib/transport/selenium-webdriver/browserstack/appAutomate.js +++ b/lib/transport/selenium-webdriver/browserstack/appAutomate.js @@ -1,5 +1,9 @@ +const path = require('path'); +const Utils = require('../../../utils'); const BrowserStack = require('./browserstack.js'); +const {Logger} = Utils; + class AppAutomate extends BrowserStack { get ApiUrl() { return `https://api.browserstack.com/${this.productNamespace}`; @@ -15,7 +19,67 @@ class AppAutomate extends BrowserStack { return this.desiredCapabilities; } - createDriver({options}) { + async uploadAppToBrowserStack(options) { + const {appUploadPath, appUploadUrl} = options; + + const multiPartFormData = {}; + if (appUploadPath) { + multiPartFormData['file'] = { + filePath: path.resolve(appUploadPath) + }; + } else if (appUploadUrl) { + multiPartFormData['url'] = { + data: appUploadUrl + }; + } + + if (options['appium:app'] && !options['appium:app'].startsWith('bs://')) { + multiPartFormData['custom_id'] = { + data: options['appium:app'] + }; + } + + // eslint-disable-next-line no-console + console.log(`Uploading app to BrowserStack from '${appUploadPath || appUploadUrl}'...`); + + try { + const response = await this.sendHttpRequest({ + url: 'https://api-cloud.browserstack.com/app-automate/upload', + method: 'POST', + use_ssl: true, + auth: { + user: this.username, + pass: this.accessKey + }, + multiPartFormData + }); + + if (response.error) { + Logger.error(response.error); + + return; + } + + // eslint-disable-next-line no-console + console.log(Logger.colors.green(Utils.symbols.ok), 'App uploaded successfully:', response, '\n'); + + if (response.app_url && !response.custom_id) { + // custom_id not being used + options['appium:app'] = response.app_url; + + // to display url when test suite is finished + this.uploadedAppUrl = response.app_url; + } + } catch (err) { + Logger.error(err); + } + } + + async createDriver({options}) { + if (options && (options.appUploadUrl || options.appUploadPath)) { + await this.uploadAppToBrowserStack(options); + } + return this.createAppiumDriver({options}); } } diff --git a/lib/transport/selenium-webdriver/browserstack/browserstack.js b/lib/transport/selenium-webdriver/browserstack/browserstack.js index ee7160acd0..2397592db8 100644 --- a/lib/transport/selenium-webdriver/browserstack/browserstack.js +++ b/lib/transport/selenium-webdriver/browserstack/browserstack.js @@ -144,6 +144,14 @@ class Browserstack extends AppiumBaseServer { console.log('\n ' + 'See more info, video, & screenshots on Browserstack:\n' + ' ' + Logger.colors.light_cyan(`https://${this.productNamespace}.browserstack.com/builds/${this.buildId}/sessions/${this.sessionId}`)); + if (this.uploadedAppUrl) { + // App was uploaded to BrowserStack and custom_id not being used + // eslint-disable-next-line no-console + console.log('\n ' + Logger.colors.light_cyan( + `Please set 'appium:app' capability to '${this.uploadedAppUrl}' to avoid uploading the app again in future runs.` + ) + '\n'); + } + this.sessionId = null; return true; From 5fdd26028cf423a6c0f597452abfb462605e9dd2 Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Mon, 23 Jan 2023 16:12:55 +0530 Subject: [PATCH 2/9] Add actionable error messages. --- .../browserstack/appAutomate.js | 48 ++++++++++++++----- lib/transport/selenium-webdriver/index.js | 2 +- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/lib/transport/selenium-webdriver/browserstack/appAutomate.js b/lib/transport/selenium-webdriver/browserstack/appAutomate.js index f405a51db1..0ec70a9263 100644 --- a/lib/transport/selenium-webdriver/browserstack/appAutomate.js +++ b/lib/transport/selenium-webdriver/browserstack/appAutomate.js @@ -13,10 +13,15 @@ class AppAutomate extends BrowserStack { return 'app-automate'; } - createSessionOptions() { + async createSessionOptions() { this.extractAppiumOptions(); - return this.desiredCapabilities; + const options = this.desiredCapabilities; + if (options && (options.appUploadUrl || options.appUploadPath)) { + await this.uploadAppToBrowserStack(options); + } + + return options; } async uploadAppToBrowserStack(options) { @@ -40,7 +45,7 @@ class AppAutomate extends BrowserStack { } // eslint-disable-next-line no-console - console.log(`Uploading app to BrowserStack from '${appUploadPath || appUploadUrl}'...`); + console.log(Logger.colors.stack_trace(`Uploading app to BrowserStack from '${appUploadPath || appUploadUrl}'...`)); try { const response = await this.sendHttpRequest({ @@ -55,15 +60,21 @@ class AppAutomate extends BrowserStack { }); if (response.error) { - Logger.error(response.error); + const errMessage = 'App upload to BrowserStack failed. Original error: ' + response.error; - return; + throw new Error(errMessage); + } + + if (!response.app_url) { + const errMessage = 'App upload was unsuccessful. Got response: ' + response; + + throw new Error(errMessage); } // eslint-disable-next-line no-console - console.log(Logger.colors.green(Utils.symbols.ok), 'App uploaded successfully:', response, '\n'); + console.log(Logger.colors.green(Utils.symbols.ok), Logger.colors.stack_trace('App upload successful!'), '\n'); - if (response.app_url && !response.custom_id) { + if (!response.custom_id) { // custom_id not being used options['appium:app'] = response.app_url; @@ -71,15 +82,30 @@ class AppAutomate extends BrowserStack { this.uploadedAppUrl = response.app_url; } } catch (err) { + err.help = []; + + if (appUploadPath) { + err.help.push('Check if you have entered correct file path in \'appUploadPath\' desired capability.'); + } else if (appUploadUrl) { + err.help.push('Check if you have entered correct publicly available file URL in \'appUploadUrl\' desired capability.'); + } + + if (err.message.includes('BROWSERSTACK_INVALID_CUSTOM_ID')) { + err.help.push('Check if \'appium:app\' or \'appium:options\' > app desired capability is correctly set to BrowserStack app url or required custom ID.'); + } + + err.help.push( + 'See BrowserStack app-upload docs for more details: https://www.browserstack.com/docs/app-automate/api-reference/appium/apps#upload-an-app', + 'More details on setting custom ID for app: https://www.browserstack.com/docs/app-automate/appium/upload-app-define-custom-id' + ); + Logger.error(err); + + throw err; } } async createDriver({options}) { - if (options && (options.appUploadUrl || options.appUploadPath)) { - await this.uploadAppToBrowserStack(options); - } - return this.createAppiumDriver({options}); } } diff --git a/lib/transport/selenium-webdriver/index.js b/lib/transport/selenium-webdriver/index.js index f32c7a13aa..fa315a463d 100644 --- a/lib/transport/selenium-webdriver/index.js +++ b/lib/transport/selenium-webdriver/index.js @@ -269,7 +269,7 @@ class Transport extends BaseTransport { const startTime = new Date(); const {host, port, start_process} = this.settings.webdriver; const portStr = port ? `port ${port}` : 'auto-generated port'; - const options = this.createSessionOptions(argv); + const options = await this.createSessionOptions(argv); if (start_process) { if (this.usingSeleniumServer) { From 5754e7194f2fcfa7c9237e5ae3666cc12bacf115 Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Mon, 23 Jan 2023 18:00:56 +0530 Subject: [PATCH 3/9] Add tests for sending multi-part form data. --- test/src/index/testRequest.js | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/test/src/index/testRequest.js b/test/src/index/testRequest.js index 366187c06d..6ba2b580af 100644 --- a/test/src/index/testRequest.js +++ b/test/src/index/testRequest.js @@ -1,6 +1,8 @@ const nock = require('nock'); const assert = require('assert'); const mockery = require('mockery'); +const fs = require('fs'); +const path = require('path'); const common = require('../../common.js'); const HttpRequest = common.require('http/request.js'); @@ -50,6 +52,13 @@ describe('test HttpRequest', function() { sessionId: '123456-789' }); + nock('https://api-cloud.browserstack.com') + .post('/app-automate/upload') + .reply(200, { + app_url: 'bs://878bdf21505f0004ce', + custom_id: 'some_app' + }); + callback(); }); @@ -405,5 +414,52 @@ describe('test HttpRequest', function() { }); + it('send POST request with multi-part form data', function(done) { + const appPath = path.resolve(__dirname, '../../extra/output/app.apk'); + const appDir = path.dirname(appPath); + + fs.mkdirSync(appDir, {recursive: true}); + fs.writeFileSync(appPath, 'app-data'); + + const options = { + method: 'POST', + url: 'https://api-cloud.browserstack.com/app-automate/upload', + use_ssl: true, + port: 443, + multiPartFormData: { + file: { + filePath: appPath + }, + custom_id: { + data: 'some_app' + } + } + }; + const request = new HttpRequest(options); + request.on('success', function () { + done(); + }).send(); + + const boundary = request.formBoundary; + const data = `\r\n--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="app.apk"\r\n\r\napp-data` + + `\r\n--${boundary}\r\nContent-Disposition: form-data; name="custom_id"\r\n\r\nsome_app` + + `\r\n--${boundary}--`; + const bufferData = Buffer.from(data); + + assert.deepStrictEqual(request.data, bufferData); + assert.strictEqual(request.contentLength, bufferData.length); + assert.strictEqual(request.use_ssl, true); + + const opts = request.reqOptions; + assert.strictEqual(opts.path, '/app-automate/upload'); + assert.strictEqual(opts.host, 'api-cloud.browserstack.com'); + assert.strictEqual(opts.port, 443); + assert.strictEqual(opts.method, 'POST'); + assert.strictEqual(opts.headers['content-type'], `multipart/form-data; boundary=${boundary}`); + assert.strictEqual(opts.headers['content-length'], bufferData.length); + assert.ok(opts.headers['User-Agent'].startsWith('nightwatch.js/')); + + fs.rmSync(appPath); + }); }); From 0a4ba719f3e4c2695105069b51db842ce76deab6 Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Mon, 23 Jan 2023 18:09:04 +0530 Subject: [PATCH 4/9] Don't show build url log if session not created. --- .../selenium-webdriver/browserstack/browserstack.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/transport/selenium-webdriver/browserstack/browserstack.js b/lib/transport/selenium-webdriver/browserstack/browserstack.js index 2397592db8..5d11e8d4a2 100644 --- a/lib/transport/selenium-webdriver/browserstack/browserstack.js +++ b/lib/transport/selenium-webdriver/browserstack/browserstack.js @@ -138,11 +138,13 @@ class Browserstack extends AppiumBaseServer { async testSuiteFinished(failures) { try { - const reason = failures instanceof Error ? `${failures.name}: ${failures.message}` : ''; - await this.sendReasonToBrowserstack(!!failures, reason); - // eslint-disable-next-line no-console - console.log('\n ' + 'See more info, video, & screenshots on Browserstack:\n' + - ' ' + Logger.colors.light_cyan(`https://${this.productNamespace}.browserstack.com/builds/${this.buildId}/sessions/${this.sessionId}`)); + if (this.sessionId) { + const reason = failures instanceof Error ? `${failures.name}: ${failures.message}` : ''; + await this.sendReasonToBrowserstack(!!failures, reason); + // eslint-disable-next-line no-console + console.log('\n ' + 'See more info, video, & screenshots on Browserstack:\n' + + ' ' + Logger.colors.light_cyan(`https://${this.productNamespace}.browserstack.com/builds/${this.buildId}/sessions/${this.sessionId}`)); + } if (this.uploadedAppUrl) { // App was uploaded to BrowserStack and custom_id not being used From 5406dd9f68636b868c8ef83082fa52ccd5dc56a4 Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Mon, 23 Jan 2023 18:12:01 +0530 Subject: [PATCH 5/9] Fix request test. --- test/src/index/testRequest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/index/testRequest.js b/test/src/index/testRequest.js index 6ba2b580af..0becdd6f0a 100644 --- a/test/src/index/testRequest.js +++ b/test/src/index/testRequest.js @@ -438,6 +438,8 @@ describe('test HttpRequest', function() { const request = new HttpRequest(options); request.on('success', function () { + fs.rmSync(appPath); + done(); }).send(); @@ -459,7 +461,5 @@ describe('test HttpRequest', function() { assert.strictEqual(opts.headers['content-type'], `multipart/form-data; boundary=${boundary}`); assert.strictEqual(opts.headers['content-length'], bufferData.length); assert.ok(opts.headers['User-Agent'].startsWith('nightwatch.js/')); - - fs.rmSync(appPath); }); }); From f8b58be8e063fe61e6b2c331fbe015387bc8b122 Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Mon, 23 Jan 2023 18:18:10 +0530 Subject: [PATCH 6/9] Fix request test final. --- lib/http/request.js | 4 ++-- .../browserstack/appAutomate.js | 1 + test/src/index/testRequest.js | 16 ++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/http/request.js b/lib/http/request.js index c486f35515..87e21d3de0 100644 --- a/lib/http/request.js +++ b/lib/http/request.js @@ -4,7 +4,6 @@ const https = require('https'); const dns = require('dns'); const HttpUtil = require('./http.js'); const HttpOptions = require('./options.js'); -const fs = require('fs'); const path = require('path'); const Auth = require('./auth.js'); const Formatter = require('./formatter.js'); @@ -418,8 +417,9 @@ class HttpRequest extends EventEmitter { bufferArray.push(Buffer.from(delimiter + crlf + header + crlf + crlf)); // set data + const {readFileSync} = require('fs'); if (fieldValue.filePath) { - bufferArray.push(fs.readFileSync(fieldValue.filePath)); + bufferArray.push(readFileSync(fieldValue.filePath)); } else { bufferArray.push(Buffer.from(fieldValue.data)); } diff --git a/lib/transport/selenium-webdriver/browserstack/appAutomate.js b/lib/transport/selenium-webdriver/browserstack/appAutomate.js index 0ec70a9263..727fcdc77a 100644 --- a/lib/transport/selenium-webdriver/browserstack/appAutomate.js +++ b/lib/transport/selenium-webdriver/browserstack/appAutomate.js @@ -52,6 +52,7 @@ class AppAutomate extends BrowserStack { url: 'https://api-cloud.browserstack.com/app-automate/upload', method: 'POST', use_ssl: true, + port: 443, auth: { user: this.username, pass: this.accessKey diff --git a/test/src/index/testRequest.js b/test/src/index/testRequest.js index 0becdd6f0a..f63bcbdfec 100644 --- a/test/src/index/testRequest.js +++ b/test/src/index/testRequest.js @@ -63,6 +63,8 @@ describe('test HttpRequest', function() { }); afterEach(function () { + mockery.deregisterAll(); + mockery.resetCache(); mockery.disable(); }); @@ -415,11 +417,11 @@ describe('test HttpRequest', function() { }); it('send POST request with multi-part form data', function(done) { - const appPath = path.resolve(__dirname, '../../extra/output/app.apk'); - const appDir = path.dirname(appPath); - - fs.mkdirSync(appDir, {recursive: true}); - fs.writeFileSync(appPath, 'app-data'); + mockery.registerMock('fs', { + readFileSync() { + return Buffer.from('app-data'); + } + }); const options = { method: 'POST', @@ -428,7 +430,7 @@ describe('test HttpRequest', function() { port: 443, multiPartFormData: { file: { - filePath: appPath + filePath: 'some/path/app.apk' }, custom_id: { data: 'some_app' @@ -438,8 +440,6 @@ describe('test HttpRequest', function() { const request = new HttpRequest(options); request.on('success', function () { - fs.rmSync(appPath); - done(); }).send(); From f3e67bc18292e00ca6fa3e4dbb4c81b08a6df4fa Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Mon, 23 Jan 2023 22:14:22 +0530 Subject: [PATCH 7/9] Add tests for changes in App Automate. --- test/src/core/testCreateSession.js | 159 ++++++++++++++++++++++++++--- 1 file changed, 146 insertions(+), 13 deletions(-) diff --git a/test/src/core/testCreateSession.js b/test/src/core/testCreateSession.js index 815471e4c4..c9b26c792d 100644 --- a/test/src/core/testCreateSession.js +++ b/test/src/core/testCreateSession.js @@ -1,5 +1,6 @@ const assert = require('assert'); const nock = require('nock'); +const path = require('path'); const Nocks = require('../../lib/nocks.js'); const Nightwatch = require('../../lib/nightwatch.js'); @@ -583,7 +584,7 @@ describe('test Request With Credentials', function () { }); }); - it('Test create session with browserstack and browserName set to null', async function () { + it('Test create session with browserstack and browserName set to null (App Automate)', async function () { nock('https://hub.browserstack.com') .post('/wd/hub/session') .reply(201, function (uri, requestBody) { @@ -592,6 +593,7 @@ describe('test Request With Credentials', function () { capabilities: { firstMatch: [{}], alwaysMatch: { + 'appium:app': 'bs://878bdf21505f0004ce', 'bstack:options': { local: 'false', userName: 'test_user', @@ -608,11 +610,22 @@ describe('test Request With Credentials', function () { return { value: { sessionId: '1352110219202', - capabilities: requestBody.capabilities + capabilities: { + platform: 'MAC', + platformName: 'iOS', + deviceName: 'iPhone 12', + realMobile: true + } } }; }); + nock('https://api-cloud.browserstack.com') + .post('/app-automate/upload') + .reply(200, { + app_url: 'bs://878bdf21505f0004ce' + }); + nock('https://api.browserstack.com') .get('/app-automate/builds.json') .reply(200, [ @@ -656,7 +669,8 @@ describe('test Request With Credentials', function () { browserName: null, chromeOptions: { w3c: false - } + }, + appUploadUrl: 'https://some_host.com/app.apk' }, parallel: false @@ -671,20 +685,139 @@ describe('test Request With Credentials', function () { assert.deepStrictEqual(result, { sessionId: '1352110219202', capabilities: { - firstMatch: [{}], - alwaysMatch: { - 'bstack:options': { - local: 'false', - userName: 'test_user', - accessKey: 'test_key', - osVersion: '14', - deviceName: 'iPhone 12', - realMobile: 'true', - buildName: 'Nightwatch Programmatic Api Demo' + platform: 'MAC', + platformName: 'iOS', + deviceName: 'iPhone 12', + realMobile: true + } + }); + + assert.strictEqual(client.transport.uploadedAppUrl, 'bs://878bdf21505f0004ce'); + }); + + it('Test create session with Browserstack App Automate using custom id', async function () { + nock('https://hub.browserstack.com') + .post('/wd/hub/session') + .reply(201, function (uri, requestBody) { + assert.deepEqual(requestBody, { + capabilities: { + firstMatch: [{}], + alwaysMatch: { + 'appium:automationName': 'UiAutomator2', + 'appium:platformVersion': '9.0', + 'appium:deviceName': 'Google Pixel 3', + 'appium:app': 'sample_app', + 'browserName': '', + 'bstack:options': { + local: 'false', + userName: 'test_user', + accessKey: 'test_key', + realMobile: true, + buildName: 'Nightwatch Programmatic Api Demo' + } + } + } + }); + + return { + value: { + sessionId: '1352110219202', + capabilities: { + platform: 'LINUX', + platformName: 'Android', + deviceName: '8B3X12Y71', + automationName: 'uiautomator2', + platformVersion: '9', + realMobile: true + } + } + }; + }); + + nock('https://api-cloud.browserstack.com') + .post('/app-automate/upload') + .reply(200, function (uri, requestBody) { + const body = requestBody.toString(); + assert.strictEqual(body.includes('name="custom_id"'), true); + assert.strictEqual(body.includes('sample_app'), true); + assert.strictEqual(body.includes('name="file"; filename="nightwatch.json"'), true); + + return { + app_url: 'bs://878bdf21505f0004ce', + custom_id: 'sample_app', + shareable_id: 'test_user/sample_app' + }; + }); + + nock('https://api.browserstack.com') + .get('/app-automate/builds.json') + .reply(200, [ + { + automation_build: { + name: 'WIN_CHROME_PROD_SANITY_LIVE_1831', + duration: 47, + status: 'running', + hashed_id: '8dd73aad3365429dec0ec12cf64c0c475a22dasds', + build_tag: null + } + }, + { + automation_build: { + name: 'External monitoring - aps - 2022-08-30', + duration: 44, + status: 'done', + hashed_id: '8dd73aad3365429dec0ec12cf64c0c475a22dasdk', + build_tag: null } } + ]); + + const client = Nightwatch.createClient({ + webdriver: { + start_process: false + }, + selenium: { + host: 'hub.browserstack.com', + port: 443 + }, + desiredCapabilities: { + 'bstack:options': { + local: 'false', + userName: 'test_user', + accessKey: 'test_key', + realMobile: true + }, + 'appium:options': { + automationName: 'UiAutomator2', + app: 'sample_app', + deviceName: 'Google Pixel 3', + platformVersion: '9.0' + }, + browserName: '', + appUploadPath: path.resolve(__dirname, '../../extra/nightwatch.json') + }, + parallel: false + }); + + client.mergeCapabilities({ + name: 'Try 1', + build: 'Nightwatch Programmatic Api Demo' + }); + + const result = await client.createSession(); + assert.deepStrictEqual(result, { + sessionId: '1352110219202', + capabilities: { + platform: 'LINUX', + platformName: 'Android', + deviceName: '8B3X12Y71', + automationName: 'uiautomator2', + platformVersion: '9', + realMobile: true } }); + + assert.strictEqual(client.transport.uploadedAppUrl, undefined); }); it('Test create session with browserstack and when buildName is not set', async function () { From 2413438b0445c38396bb1231d39420e929d69bb2 Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Mon, 23 Jan 2023 22:27:32 +0530 Subject: [PATCH 8/9] Small fix. --- lib/transport/selenium-webdriver/browserstack/appAutomate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/transport/selenium-webdriver/browserstack/appAutomate.js b/lib/transport/selenium-webdriver/browserstack/appAutomate.js index 727fcdc77a..988a57e766 100644 --- a/lib/transport/selenium-webdriver/browserstack/appAutomate.js +++ b/lib/transport/selenium-webdriver/browserstack/appAutomate.js @@ -106,7 +106,7 @@ class AppAutomate extends BrowserStack { } } - async createDriver({options}) { + createDriver({options}) { return this.createAppiumDriver({options}); } } From d79837351ebe29226fe6ad0d03e64121c50d5914 Mon Sep 17 00:00:00 2001 From: Andrei Rusu Date: Tue, 7 Feb 2023 10:31:12 +0100 Subject: [PATCH 9/9] Update request.js --- lib/http/request.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/http/request.js b/lib/http/request.js index 87e21d3de0..c0bd74a84c 100644 --- a/lib/http/request.js +++ b/lib/http/request.js @@ -2,9 +2,10 @@ const EventEmitter = require('events'); const http = require('http'); const https = require('https'); const dns = require('dns'); +const path = require('path'); + const HttpUtil = require('./http.js'); const HttpOptions = require('./options.js'); -const path = require('path'); const Auth = require('./auth.js'); const Formatter = require('./formatter.js'); const HttpResponse = require('./response.js');