diff --git a/packages/webdriver-utils/src/providers/automateProvider.js b/packages/webdriver-utils/src/providers/automateProvider.js index 10f980624..fbb8fa85b 100644 --- a/packages/webdriver-utils/src/providers/automateProvider.js +++ b/packages/webdriver-utils/src/providers/automateProvider.js @@ -13,11 +13,25 @@ export default class AutomateProvider extends GenericProvider { return commandExecutorUrl.includes(process.env.AA_DOMAIN || 'browserstack'); } + // Automate capture reads the screen size, DPR and regions through JavaScript, which a + // native app context cannot run (App Automate answers 405 Method Not Allowed). Browser + // sessions always carry a browserName; native ones carry an app identifier instead. + static isNativeAppSession(caps) { + if (!caps || caps.browserName) return false; + const desired = caps.desired || {}; + return ['app', 'appium:app', 'appPackage', 'bundleId', 'bundleID'] + .some(key => caps[key] || desired[key]); + } + async createDriver() { this.driver = new Driver(this.sessionId, this.commandExecutorUrl, this.capabilities); log.debug(`Passed capabilities -> ${JSON.stringify(this.capabilities)}`); const caps = await this.driver.getCapabilites(); log.debug(`Fetched capabilities -> ${JSON.stringify(caps)}`); + if (AutomateProvider.isNativeAppSession(caps)) { + throw new Error('Percy on Automate captures browser sessions; this is a native app session. ' + + 'Use App Percy for native apps (an app project token with `percy app:exec`).'); + } this.metaData = MetaDataResolver.resolve(this.driver, caps, this.capabilities); } diff --git a/packages/webdriver-utils/test/providers/automateProvider.test.js b/packages/webdriver-utils/test/providers/automateProvider.test.js index ba25977d3..0c0ee2020 100644 --- a/packages/webdriver-utils/test/providers/automateProvider.test.js +++ b/packages/webdriver-utils/test/providers/automateProvider.test.js @@ -70,6 +70,41 @@ describe('AutomateProvider', () => { }); }); + describe('createDriver on a native app session', () => { + const nativeError = 'Percy on Automate captures browser sessions; this is a native app session. ' + + 'Use App Percy for native apps (an app project token with `percy app:exec`).'; + + it('rejects an Android native session', async () => { + spyOn(Driver.prototype, 'getCapabilites').and.returnValue(Promise.resolve({ + platformName: 'Android', appPackage: 'org.wikipedia.alpha', automationName: 'uiautomator2' + })); + await expectAsync(new AutomateProvider(args).createDriver()).toBeRejectedWithError(nativeError); + }); + + it('rejects an iOS native session reporting an empty browserName', async () => { + spyOn(Driver.prototype, 'getCapabilites').and.returnValue(Promise.resolve({ + platformName: 'iOS', browserName: '', desired: { bundleId: 'com.example.app' } + })); + await expectAsync(new AutomateProvider(args).createDriver()).toBeRejectedWithError(nativeError); + }); + + it('accepts a mobile browser session', async () => { + spyOn(Driver.prototype, 'getCapabilites').and.returnValue(Promise.resolve({ + platformName: 'Android', browserName: 'chrome', appPackage: 'com.android.chrome' + })); + const automateProvider = new AutomateProvider(args); + await automateProvider.createDriver(); + expect(automateProvider.metaData).toBeDefined(); + }); + + it('accepts a session without browserName or an app identifier', async () => { + spyOn(Driver.prototype, 'getCapabilites').and.returnValue(Promise.resolve({ platformName: 'Android' })); + const automateProvider = new AutomateProvider(args); + await automateProvider.createDriver(); + expect(automateProvider.metaData).toBeDefined(); + }); + }); + describe('supports', () => { it('returns true for browserstack automate', () => { expect(AutomateProvider.supports('http:browserstack')).toEqual(true);