diff --git a/.eslintrc b/.eslintrc index 6f25e6bd3..e949aee65 100644 --- a/.eslintrc +++ b/.eslintrc @@ -11,11 +11,7 @@ "rules": {"func-names": "off"} }, { - "files": [ - "./packages/*/index.js", - "./packages/*/scripts/**/*.js", - "./test/*.js" - ], + "files": ["./packages/*/index.js", "./packages/*/scripts/**/*.js", "./test/*.js"], "parserOptions": { "sourceType": "script" } diff --git a/.mocharc.js b/.mocharc.js index 1397cb4ff..eb2b7f7b2 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -3,10 +3,8 @@ 'use strict'; module.exports = { - require: [ - require.resolve('./test/setup.js') - ], - // forbids use of .only() in CI + require: [require.resolve('./test/setup.js')], + // forbids use of .only() in CI forbidOnly: Boolean(process.env.CI), - color: true + color: true, }; diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..b43e4427f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,12 @@ +**/node_modules/** +**/build/** +**/fixtures/** +**/*.min.* +**/*.md +**/*.yml +**/*.json +**/.vscode/** +**/*.html +**/generated/** +# generated +packages/types/lib/appium-config.ts diff --git a/ci-jobs/scripts/parse-tag.js b/ci-jobs/scripts/parse-tag.js index 246516924..80c521a4e 100644 --- a/ci-jobs/scripts/parse-tag.js +++ b/ci-jobs/scripts/parse-tag.js @@ -1,3 +1,3 @@ const semver = require('semver'); -const { version } = require('../../package.json'); -console.log(semver(version).prerelease[0]); //eslint-disable-line no-console \ No newline at end of file +const {version} = require('../../package.json'); +console.log(semver(version).prerelease[0]); //eslint-disable-line no-console diff --git a/ci-jobs/scripts/set-package-json-version.js b/ci-jobs/scripts/set-package-json-version.js index e7bb1a98c..26b0b067b 100644 --- a/ci-jobs/scripts/set-package-json-version.js +++ b/ci-jobs/scripts/set-package-json-version.js @@ -1,17 +1,17 @@ const path = require('path'); -const { fs, logger } = require('appium-support'); -const { asyncify } = require('asyncbox'); +const {fs, logger} = require('appium-support'); +const {asyncify} = require('asyncbox'); const packageJson = require('../../package.json'); const log = new logger.getLogger('Create Release Branch:'); -async function setPackageJsonVersion (version = `${process.env.MINOR_BRANCH_NAME}.0-rc.0`) { +async function setPackageJsonVersion(version = `${process.env.MINOR_BRANCH_NAME}.0-rc.0`) { packageJson.version = version; log.info(`Setting version to: ${version}`); await fs.writeFile( path.resolve(__dirname, '..', '..', 'package.json'), JSON.stringify(packageJson, null, 2), - 'utf8', + 'utf8' ); } @@ -19,4 +19,4 @@ if (require.main === module) { asyncify(setPackageJsonVersion); } -module.exports = setPackageJsonVersion; \ No newline at end of file +module.exports = setPackageJsonVersion; diff --git a/docs/toc.js b/docs/toc.js index 6b820dea0..1dec9bea3 100644 --- a/docs/toc.js +++ b/docs/toc.js @@ -1,415 +1,624 @@ module.exports = { en: [ ['Home', 'about-appium/intro.md'], - ['About', ['about-appium', - ['Introduction', 'intro.md'], - ['The Appium Clients', 'appium-clients.md'], - ['Getting Started', 'getting-started.md'], - ['Supported Platforms', 'platform-support.md'], - ['API Documentation', 'api.md'], - ['Appium 1.x EOL Plan', '1.x-eol.md'], - ]], - ['Drivers', ['drivers', - ['Driver CLI', 'driver-cli.md'], - ['XCUITest (iOS)', 'ios-xcuitest.md'], - ['XCUITest Real Devices (iOS)', 'ios-xcuitest-real-devices.md'], - ['UIAutomation (iOS)', 'ios-uiautomation.md'], - ['UIAutomation Safari Launcher (iOS)', 'ios-uiautomation-safari-launcher.md'], - ['UIAutomator (Android)', 'android-uiautomator.md'], - ['UIAutomator2 (Android)', 'android-uiautomator2.md'], - ['Espresso (Android)', 'android-espresso.md'], - ['Windows', 'windows.md'], - ['Safari (Mac/iOS)', 'safari.md'], - ['Gecko (Firefox)', 'gecko.md'], - ['Mac', 'mac.md'], - ['Mac2', 'mac2.md'], - ]], - ['Commands', ['commands', - ['Status', 'status.md'], - ['Execute Mobile Command', 'mobile-command.md'], - ['Session', ['session', - ['Create', 'create.md'], - ['End', 'delete.md'], - ['Get Session Capabilities', 'get.md'], - ['Go Back', 'back.md'], - ['Screenshot', 'screenshot.md'], - ['Source', 'source.md'], - ['Timeouts', ['timeouts', - ['Timeouts', 'timeouts.md'], - ['Implicit Wait', 'implicit-wait.md'], - ['Async Script', 'async-script.md'], - ]], - ['Orientation', ['orientation', - ['Get Orientation', 'get-orientation.md'], - ['Set Orientation', 'set-orientation.md'], - ]], - ['Geolocation', ['geolocation', - ['Get Geolocation', 'get-geolocation.md'], - ['Set Geolocation', 'set-geolocation.md'], - ]], - ['Logs', ['logs', - ['Get Log Types', 'get-log-types.md'], - ['Get Logs', 'get-log.md'], - ]], - ['Events', ['events', - ['Log event', 'log-event.md'], - ['Get events', 'get-events.md'], - ]], - ['Settings', ['settings', - ['Update Settings', 'update-settings.md'], - ['Get Device Settings', 'get-settings.md'], - ]], - ['Execute Driver Script', 'execute-driver.md'], - ]], - ['Device', ['device', - ['Activity', ['activity', - ['Start Activity', 'start-activity.md'], - ['Current Activity', 'current-activity.md'], - ['Current Package', 'current-package.md'], - ]], - ['App', ['app', - ['Install App', 'install-app.md'], - ['Is App Installed', 'is-app-installed.md'], - ['Launch App', 'launch-app.md'], - ['Background App', 'background-app.md'], - ['Close App', 'close-app.md'], - ['Reset App', 'reset-app.md'], - ['Remove App', 'remove-app.md'], - ['Activate App', 'activate-app.md'], - ['Terminate App', 'terminate-app.md'], - ['Get App State', 'app-state.md'], - ['Get App Strings', 'get-app-strings.md'], - ['End Test Coverage', 'end-test-coverage.md'], - ]], - ['Clipboard', ['clipboard', - ['Get Clipboard', 'get-clipboard.md'], - ['Set Clipboard', 'set-clipboard.md'], - ]], - ['Emulator', ['emulator', - ['Power AC', 'power_ac.md'], - ['Power Capacity', 'power_capacity.md'], - ]], - ['Files', ['files', - ['Push File', 'push-file.md'], - ['Pull File', 'pull-file.md'], - ['Pull Folder', 'pull-folder.md'], - ]], - ['Interactions', ['interactions', - ['Shake', 'shake.md'], - ['Lock', 'lock.md'], - ['Unlock', 'unlock.md'], - ['Is Locked', 'is-locked.md'], - ['Rotate', 'rotate.md'], - ]], - ['Keys', ['keys', - ['Press keycode', 'press-keycode.md'], - ['Long press keycode', 'long-press-keycode.md'], - ['Hide Keyboard', 'hide-keyboard.md'], - ['Is Keyboard Shown', 'is-keyboard-shown.md'], - ]], - ['Network', ['network', - ['Toggle Airplane Mode', 'toggle-airplane-mode.md'], - ['Toggle Data', 'toggle-data.md'], - ['Toggle WiFi', 'toggle-wifi.md'], - ['Toggle Location Services', 'toggle-location-services.md'], - ['Send SMS', 'send-sms.md'], - ['GSM Call', 'gsm-call.md'], - ['GSM Signal', 'gsm-signal.md'], - ['GSM Voice', 'gsm-voice.md'], - ['Network Speed', 'network-speed.md'], - ]], - ['Performance Data', ['performance-data', - ['Get Performance Data', 'get-performance-data.md'], - ['Performance Data Types', 'performance-data-types.md'], - ]], - ['Screen Recording', ['recording-screen', - ['Start Screen Recording', 'start-recording-screen.md'], - ['Stop Screen Recording', 'stop-recording-screen.md'], - ]], - ['Simulator', ['simulator', - ['Perform Touch ID', 'touch-id.md'], - ['Toggle Touch ID Enrollment', 'toggle-touch-id-enrollment.md'], - ]], - ['System', ['system', - ['Open Notifications', 'open-notifications.md'], - ['System Bars', 'system-bars.md'], - ['System Time', 'system-time.md'], - ['Display density', 'display-density.md'], - ]], - ['Authentication', ['authentication', - ['Finger Print', 'finger-print.md'], - ]], - ]], - ['Element', ['element', - ['Find Element', 'find-element.md'], - ['Find Elements', 'find-elements.md'], - ['Actions', ['actions', - ['Click', 'click.md'], - ['Send Keys', 'send-keys.md'], - ['Clear', 'clear.md'], - ]], - ['Attributes', ['attributes', - ['Text', 'text.md'], - ['Name', 'name.md'], - ['Attribute', 'attribute.md'], - ['Selected', 'selected.md'], - ['Enabled', 'enabled.md'], - ['Displayed', 'displayed.md'], - ['Location', 'location.md'], - ['Size', 'size.md'], - ['Rect', 'rect.md'], - ['CSS Property', 'css-property.md'], - ['Location in View', 'location-in-view.md'], - ]], - ['Other', ['other', - ['Submit', 'submit.md'], - ['Active Element', 'active.md'], - ['Equals Element', 'equals-element.md'], - ]] - ]], - ['Context', ['context', - ['Get Context', 'get-context.md'], - ['Get All Contexts', 'get-contexts.md'], - ['Set Context', 'set-context.md'], - ]], - ['Interactions', ['interactions', - ['Mouse', ['mouse', - ['Move To', 'moveto.md'], - ['Click', 'click.md'], - ['Double Click', 'doubleclick.md'], - ['Button Down', 'button-down.md'], - ['Button Up', 'button-up.md'], - ]], - ['Touch', ['touch', - ['Single Tap', 'tap.md'], - ['Double Tap', 'double-tap.md'], - ['Move', 'move.md'], - ['Touch Down', 'touch-down.md'], - ['Touch Up', 'touch-up.md'], - ['Long Press', 'long-press.md'], - ['Scroll', 'scroll.md'], - ['Flick', 'flick.md'], - ['Multi Touch Perform', 'multi-touch-perform.md'], - ['Touch Perform', 'touch-perform.md'], - ]], - ['W3C Actions', 'actions.md'], - ]], - ['Web', ['web', - ['Window', ['window', - ['Set Window', 'set-window.md'], - ['Close Window', 'close-window.md'], - ['Get Handle', 'get-handle.md'], - ['Get Handles', 'get-handles.md'], - ['Get Title', 'title.md'], - ['Get Window Size', 'get-window-size.md'], - ['Set Window Size', 'set-window-size.md'], - ['Get Window Position', 'get-window-position.md'], - ['Set Window Position', 'set-window-position.md'], - ['Maximize Window', 'maximize-window.md'], - ]], - ['Navigation', ['navigation', - ['Go to URL', 'go-to-url.md'], - ['Get URL', 'url.md'], - ['Back', 'back.md'], - ['Forward', 'forward.md'], - ['Refresh', 'refresh.md'], - ]], - ['Storage', ['storage', - ['Get All Cookies', 'get-all-cookies.md'], - ['Set Cookie', 'set-cookie.md'], - ['Delete Cookie', 'delete-cookie.md'], - ['Delete All Cookies', 'delete-all-cookies.md'], - ]], - ['Frame', ['frame', - ['Switch to Frame', 'switch.md'], - ['Switch to Parent Frame', 'parent.md'], - ]], - ['Execute Async', 'execute-async.md'], - ['Execute', 'execute.md'], - ]], - ]], - ['Writing & Running Tests', ['writing-running-appium', - ['Running Tests', 'running-tests.md'], - ['Desired Capabilities', 'caps.md'], - ['The --default-capabilities flag', 'default-capabilities-arg.md'], - ['Finding Elements', 'finding-elements.md'], - ['Touch Actions', 'touch-actions.md'], - ['CLI Arguments', 'server-args.md'], - ['Server Security', 'security.md'], - ['Web/Web Views', ['web', - ['Mobile Web Testing', 'mobile-web.md'], - ['Automating Hybrid Apps', 'hybrid.md'], - ['Using Chromedriver', 'chromedriver.md'], - ]], - ['Image Comparison', 'image-comparison.md'], - ['iOS', ['ios', - ['Low-Level Insights on iOS Input Events', 'actions.md'], - ['XCUITest Mobile Gestures', 'ios-xctest-mobile-gestures.md'], - ['XCUITest Mobile App Management', 'ios-xctest-mobile-apps-management.md'], - ['iOS Pasteboard Guide', 'ios-xctest-pasteboard.md'], - ['iOS Predicate Guide', 'ios-predicate.md'], - ['iOS Touch ID Guide', 'ios-touch-id.md'], - ['iOS Install Certificate', 'ios-xctest-install-certificate.md'], - ['tvOS support', 'ios-tvos.md'], - ['Pushing/Pulling files', 'ios-xctest-file-movement.md'], - ['Audio Capture', 'audio-capture.md'], - ]], - ['Android', ['android', - ['Low-Level Insights on Android Input Events', 'actions.md'], - ['UiSelector Guide', 'uiautomator-uiselector.md'], - ['Espresso Datamatcher Guide', 'espresso-datamatcher-selector.md'], - ['Android Code Coverage Guide', 'android-coverage.md'], - ['Activities Startup Troubleshooting Guide', 'activity-startup.md'], - ['How To Execute Shell Commands On The Remote Device', 'android-shell.md'], - ['Android Device Screen Streaming', 'android-screen-streaming.md'], - ['Automating Mobile Gestures With UiAutomator2 Backend', 'android-mobile-gestures.md'], - ['How To Emulate IME Actions Generation', 'android-ime.md'], - ['How To Test Android App Bundle', 'android-appbundle.md'] - ]], - ['Other', ['other', - ['Reset Strategies', 'reset-strategies.md'], - ['Network Connection Guide', 'network-connection.md'], - ['Using Unicode with Appium', 'unicode.md'], - ['Troubleshooting', 'troubleshooting.md'] - ]], - ['Tutorial', ['tutorial', - ['Swipe Tutorial', 'swipe-tutorial.md'], - ['Simple swipe', ['swipe', - ['Screen', 'simple-screen.md'], - ['Element', 'simple-element.md'], - ['Partial screen', 'simple-partial-screen.md'] - ]], - ['Android swipe', ['swipe', - ['Simple', 'android-simple.md'], - ['Multiple scroll views', 'android-multiple.md'], - ['Add scroll layout', 'android-layout-direction.md'], - ['Tricks and Tips', 'android-tricks.md'] - ]], - ['iOS swipe', ['swipe', - ['Screen', 'ios-mobile-screen.md'], - ['Element', 'ios-mobile-element.md'], - ['Element search', 'ios-mobile-element-search.md'] - ]], - ['iOS pickerWheels', ['swipe', - ['Fast', 'ios-picker-wheels-set-value.md'], - ['Slow', 'ios-picker-wheels-mobile.md'] - ]], - ['Troubleshoot', ['swipe', - ['Guide', 'swipe-troubleshoot-guide.md'] - ]] - ]], - ]], - ['Advanced', ['advanced-concepts', - ['Migrating to Appium 2.x', 'migrating-to-appium-2.0.md'], - ['Finding Image Elements', 'image-elements.md'], - ['Using Element Finding Plugins', 'element-finding-plugins.md'], - ['Migrating to XCUITest', 'migrating-to-xcuitest.md'], - ['Using Selenium Grid with Appium', 'grid.md'], - ['Appium Logs Filtering', 'log-filters.md'], - ['Cross-domain iframes', 'cross-domain-iframes.md'], - ['Using a custom WDA server', 'wda-custom-server.md'], - ['Running with multiple versions of Xcode', 'multiple-xcode-versions.md'], - ['The Event Timings API', 'event-timings.md'], - ['Setup for Parallel Testing', 'parallel-tests.md'], - ['The Settings API', 'settings.md'], - ['Memory Collection', 'memory-collection.md']]], - ['Contributing', ['contributing-to-appium', - ['Running Appium from Source', 'appium-from-source.md'], - ['Developer Overview', 'developers-overview.md'], - ['Standard Dev Commands', 'dev-tools.md'], - ['Appium Style Guide', 'style-guide.md'], - ['How to Write Docs', 'how-to-write-docs.md'], - ['Appium Package Structure', 'appium-packages.md'], - ['Release Appium', 'release-appium.md'], - ['Credits', 'credits.md']]] + [ + 'About', + [ + 'about-appium', + ['Introduction', 'intro.md'], + ['The Appium Clients', 'appium-clients.md'], + ['Getting Started', 'getting-started.md'], + ['Supported Platforms', 'platform-support.md'], + ['API Documentation', 'api.md'], + ['Appium 1.x EOL Plan', '1.x-eol.md'], + ], + ], + [ + 'Drivers', + [ + 'drivers', + ['Driver CLI', 'driver-cli.md'], + ['XCUITest (iOS)', 'ios-xcuitest.md'], + ['XCUITest Real Devices (iOS)', 'ios-xcuitest-real-devices.md'], + ['UIAutomation (iOS)', 'ios-uiautomation.md'], + ['UIAutomation Safari Launcher (iOS)', 'ios-uiautomation-safari-launcher.md'], + ['UIAutomator (Android)', 'android-uiautomator.md'], + ['UIAutomator2 (Android)', 'android-uiautomator2.md'], + ['Espresso (Android)', 'android-espresso.md'], + ['Windows', 'windows.md'], + ['Safari (Mac/iOS)', 'safari.md'], + ['Gecko (Firefox)', 'gecko.md'], + ['Mac', 'mac.md'], + ['Mac2', 'mac2.md'], + ], + ], + [ + 'Commands', + [ + 'commands', + ['Status', 'status.md'], + ['Execute Mobile Command', 'mobile-command.md'], + [ + 'Session', + [ + 'session', + ['Create', 'create.md'], + ['End', 'delete.md'], + ['Get Session Capabilities', 'get.md'], + ['Go Back', 'back.md'], + ['Screenshot', 'screenshot.md'], + ['Source', 'source.md'], + [ + 'Timeouts', + [ + 'timeouts', + ['Timeouts', 'timeouts.md'], + ['Implicit Wait', 'implicit-wait.md'], + ['Async Script', 'async-script.md'], + ], + ], + [ + 'Orientation', + [ + 'orientation', + ['Get Orientation', 'get-orientation.md'], + ['Set Orientation', 'set-orientation.md'], + ], + ], + [ + 'Geolocation', + [ + 'geolocation', + ['Get Geolocation', 'get-geolocation.md'], + ['Set Geolocation', 'set-geolocation.md'], + ], + ], + ['Logs', ['logs', ['Get Log Types', 'get-log-types.md'], ['Get Logs', 'get-log.md']]], + ['Events', ['events', ['Log event', 'log-event.md'], ['Get events', 'get-events.md']]], + [ + 'Settings', + [ + 'settings', + ['Update Settings', 'update-settings.md'], + ['Get Device Settings', 'get-settings.md'], + ], + ], + ['Execute Driver Script', 'execute-driver.md'], + ], + ], + [ + 'Device', + [ + 'device', + [ + 'Activity', + [ + 'activity', + ['Start Activity', 'start-activity.md'], + ['Current Activity', 'current-activity.md'], + ['Current Package', 'current-package.md'], + ], + ], + [ + 'App', + [ + 'app', + ['Install App', 'install-app.md'], + ['Is App Installed', 'is-app-installed.md'], + ['Launch App', 'launch-app.md'], + ['Background App', 'background-app.md'], + ['Close App', 'close-app.md'], + ['Reset App', 'reset-app.md'], + ['Remove App', 'remove-app.md'], + ['Activate App', 'activate-app.md'], + ['Terminate App', 'terminate-app.md'], + ['Get App State', 'app-state.md'], + ['Get App Strings', 'get-app-strings.md'], + ['End Test Coverage', 'end-test-coverage.md'], + ], + ], + [ + 'Clipboard', + [ + 'clipboard', + ['Get Clipboard', 'get-clipboard.md'], + ['Set Clipboard', 'set-clipboard.md'], + ], + ], + [ + 'Emulator', + ['emulator', ['Power AC', 'power_ac.md'], ['Power Capacity', 'power_capacity.md']], + ], + [ + 'Files', + [ + 'files', + ['Push File', 'push-file.md'], + ['Pull File', 'pull-file.md'], + ['Pull Folder', 'pull-folder.md'], + ], + ], + [ + 'Interactions', + [ + 'interactions', + ['Shake', 'shake.md'], + ['Lock', 'lock.md'], + ['Unlock', 'unlock.md'], + ['Is Locked', 'is-locked.md'], + ['Rotate', 'rotate.md'], + ], + ], + [ + 'Keys', + [ + 'keys', + ['Press keycode', 'press-keycode.md'], + ['Long press keycode', 'long-press-keycode.md'], + ['Hide Keyboard', 'hide-keyboard.md'], + ['Is Keyboard Shown', 'is-keyboard-shown.md'], + ], + ], + [ + 'Network', + [ + 'network', + ['Toggle Airplane Mode', 'toggle-airplane-mode.md'], + ['Toggle Data', 'toggle-data.md'], + ['Toggle WiFi', 'toggle-wifi.md'], + ['Toggle Location Services', 'toggle-location-services.md'], + ['Send SMS', 'send-sms.md'], + ['GSM Call', 'gsm-call.md'], + ['GSM Signal', 'gsm-signal.md'], + ['GSM Voice', 'gsm-voice.md'], + ['Network Speed', 'network-speed.md'], + ], + ], + [ + 'Performance Data', + [ + 'performance-data', + ['Get Performance Data', 'get-performance-data.md'], + ['Performance Data Types', 'performance-data-types.md'], + ], + ], + [ + 'Screen Recording', + [ + 'recording-screen', + ['Start Screen Recording', 'start-recording-screen.md'], + ['Stop Screen Recording', 'stop-recording-screen.md'], + ], + ], + [ + 'Simulator', + [ + 'simulator', + ['Perform Touch ID', 'touch-id.md'], + ['Toggle Touch ID Enrollment', 'toggle-touch-id-enrollment.md'], + ], + ], + [ + 'System', + [ + 'system', + ['Open Notifications', 'open-notifications.md'], + ['System Bars', 'system-bars.md'], + ['System Time', 'system-time.md'], + ['Display density', 'display-density.md'], + ], + ], + ['Authentication', ['authentication', ['Finger Print', 'finger-print.md']]], + ], + ], + [ + 'Element', + [ + 'element', + ['Find Element', 'find-element.md'], + ['Find Elements', 'find-elements.md'], + [ + 'Actions', + [ + 'actions', + ['Click', 'click.md'], + ['Send Keys', 'send-keys.md'], + ['Clear', 'clear.md'], + ], + ], + [ + 'Attributes', + [ + 'attributes', + ['Text', 'text.md'], + ['Name', 'name.md'], + ['Attribute', 'attribute.md'], + ['Selected', 'selected.md'], + ['Enabled', 'enabled.md'], + ['Displayed', 'displayed.md'], + ['Location', 'location.md'], + ['Size', 'size.md'], + ['Rect', 'rect.md'], + ['CSS Property', 'css-property.md'], + ['Location in View', 'location-in-view.md'], + ], + ], + [ + 'Other', + [ + 'other', + ['Submit', 'submit.md'], + ['Active Element', 'active.md'], + ['Equals Element', 'equals-element.md'], + ], + ], + ], + ], + [ + 'Context', + [ + 'context', + ['Get Context', 'get-context.md'], + ['Get All Contexts', 'get-contexts.md'], + ['Set Context', 'set-context.md'], + ], + ], + [ + 'Interactions', + [ + 'interactions', + [ + 'Mouse', + [ + 'mouse', + ['Move To', 'moveto.md'], + ['Click', 'click.md'], + ['Double Click', 'doubleclick.md'], + ['Button Down', 'button-down.md'], + ['Button Up', 'button-up.md'], + ], + ], + [ + 'Touch', + [ + 'touch', + ['Single Tap', 'tap.md'], + ['Double Tap', 'double-tap.md'], + ['Move', 'move.md'], + ['Touch Down', 'touch-down.md'], + ['Touch Up', 'touch-up.md'], + ['Long Press', 'long-press.md'], + ['Scroll', 'scroll.md'], + ['Flick', 'flick.md'], + ['Multi Touch Perform', 'multi-touch-perform.md'], + ['Touch Perform', 'touch-perform.md'], + ], + ], + ['W3C Actions', 'actions.md'], + ], + ], + [ + 'Web', + [ + 'web', + [ + 'Window', + [ + 'window', + ['Set Window', 'set-window.md'], + ['Close Window', 'close-window.md'], + ['Get Handle', 'get-handle.md'], + ['Get Handles', 'get-handles.md'], + ['Get Title', 'title.md'], + ['Get Window Size', 'get-window-size.md'], + ['Set Window Size', 'set-window-size.md'], + ['Get Window Position', 'get-window-position.md'], + ['Set Window Position', 'set-window-position.md'], + ['Maximize Window', 'maximize-window.md'], + ], + ], + [ + 'Navigation', + [ + 'navigation', + ['Go to URL', 'go-to-url.md'], + ['Get URL', 'url.md'], + ['Back', 'back.md'], + ['Forward', 'forward.md'], + ['Refresh', 'refresh.md'], + ], + ], + [ + 'Storage', + [ + 'storage', + ['Get All Cookies', 'get-all-cookies.md'], + ['Set Cookie', 'set-cookie.md'], + ['Delete Cookie', 'delete-cookie.md'], + ['Delete All Cookies', 'delete-all-cookies.md'], + ], + ], + [ + 'Frame', + ['frame', ['Switch to Frame', 'switch.md'], ['Switch to Parent Frame', 'parent.md']], + ], + ['Execute Async', 'execute-async.md'], + ['Execute', 'execute.md'], + ], + ], + ], + ], + [ + 'Writing & Running Tests', + [ + 'writing-running-appium', + ['Running Tests', 'running-tests.md'], + ['Desired Capabilities', 'caps.md'], + ['The --default-capabilities flag', 'default-capabilities-arg.md'], + ['Finding Elements', 'finding-elements.md'], + ['Touch Actions', 'touch-actions.md'], + ['CLI Arguments', 'server-args.md'], + ['Server Security', 'security.md'], + [ + 'Web/Web Views', + [ + 'web', + ['Mobile Web Testing', 'mobile-web.md'], + ['Automating Hybrid Apps', 'hybrid.md'], + ['Using Chromedriver', 'chromedriver.md'], + ], + ], + ['Image Comparison', 'image-comparison.md'], + [ + 'iOS', + [ + 'ios', + ['Low-Level Insights on iOS Input Events', 'actions.md'], + ['XCUITest Mobile Gestures', 'ios-xctest-mobile-gestures.md'], + ['XCUITest Mobile App Management', 'ios-xctest-mobile-apps-management.md'], + ['iOS Pasteboard Guide', 'ios-xctest-pasteboard.md'], + ['iOS Predicate Guide', 'ios-predicate.md'], + ['iOS Touch ID Guide', 'ios-touch-id.md'], + ['iOS Install Certificate', 'ios-xctest-install-certificate.md'], + ['tvOS support', 'ios-tvos.md'], + ['Pushing/Pulling files', 'ios-xctest-file-movement.md'], + ['Audio Capture', 'audio-capture.md'], + ], + ], + [ + 'Android', + [ + 'android', + ['Low-Level Insights on Android Input Events', 'actions.md'], + ['UiSelector Guide', 'uiautomator-uiselector.md'], + ['Espresso Datamatcher Guide', 'espresso-datamatcher-selector.md'], + ['Android Code Coverage Guide', 'android-coverage.md'], + ['Activities Startup Troubleshooting Guide', 'activity-startup.md'], + ['How To Execute Shell Commands On The Remote Device', 'android-shell.md'], + ['Android Device Screen Streaming', 'android-screen-streaming.md'], + ['Automating Mobile Gestures With UiAutomator2 Backend', 'android-mobile-gestures.md'], + ['How To Emulate IME Actions Generation', 'android-ime.md'], + ['How To Test Android App Bundle', 'android-appbundle.md'], + ], + ], + [ + 'Other', + [ + 'other', + ['Reset Strategies', 'reset-strategies.md'], + ['Network Connection Guide', 'network-connection.md'], + ['Using Unicode with Appium', 'unicode.md'], + ['Troubleshooting', 'troubleshooting.md'], + ], + ], + [ + 'Tutorial', + [ + 'tutorial', + ['Swipe Tutorial', 'swipe-tutorial.md'], + [ + 'Simple swipe', + [ + 'swipe', + ['Screen', 'simple-screen.md'], + ['Element', 'simple-element.md'], + ['Partial screen', 'simple-partial-screen.md'], + ], + ], + [ + 'Android swipe', + [ + 'swipe', + ['Simple', 'android-simple.md'], + ['Multiple scroll views', 'android-multiple.md'], + ['Add scroll layout', 'android-layout-direction.md'], + ['Tricks and Tips', 'android-tricks.md'], + ], + ], + [ + 'iOS swipe', + [ + 'swipe', + ['Screen', 'ios-mobile-screen.md'], + ['Element', 'ios-mobile-element.md'], + ['Element search', 'ios-mobile-element-search.md'], + ], + ], + [ + 'iOS pickerWheels', + [ + 'swipe', + ['Fast', 'ios-picker-wheels-set-value.md'], + ['Slow', 'ios-picker-wheels-mobile.md'], + ], + ], + ['Troubleshoot', ['swipe', ['Guide', 'swipe-troubleshoot-guide.md']]], + ], + ], + ], + ], + [ + 'Advanced', + [ + 'advanced-concepts', + ['Migrating to Appium 2.x', 'migrating-to-appium-2.0.md'], + ['Finding Image Elements', 'image-elements.md'], + ['Using Element Finding Plugins', 'element-finding-plugins.md'], + ['Migrating to XCUITest', 'migrating-to-xcuitest.md'], + ['Using Selenium Grid with Appium', 'grid.md'], + ['Appium Logs Filtering', 'log-filters.md'], + ['Cross-domain iframes', 'cross-domain-iframes.md'], + ['Using a custom WDA server', 'wda-custom-server.md'], + ['Running with multiple versions of Xcode', 'multiple-xcode-versions.md'], + ['The Event Timings API', 'event-timings.md'], + ['Setup for Parallel Testing', 'parallel-tests.md'], + ['The Settings API', 'settings.md'], + ['Memory Collection', 'memory-collection.md'], + ], + ], + [ + 'Contributing', + [ + 'contributing-to-appium', + ['Running Appium from Source', 'appium-from-source.md'], + ['Developer Overview', 'developers-overview.md'], + ['Standard Dev Commands', 'dev-tools.md'], + ['Appium Style Guide', 'style-guide.md'], + ['How to Write Docs', 'how-to-write-docs.md'], + ['Appium Package Structure', 'appium-packages.md'], + ['Release Appium', 'release-appium.md'], + ['Credits', 'credits.md'], + ], + ], ], cn: [ - ['关于', ['about-appium', - ['简介', 'intro.md'], - ['Appium 客户端', 'appium-clients.md'], - // ['入门指南', 'getting-started.md'], - ['已支持的平台', 'platform-support.md'], - // ['API 文档', 'api.md'], - ]], - ['驱动程序', ['drivers', - ['Driver CLI', 'driver-cli.md'], - ['XCUITest (iOS)', 'ios-xcuitest.md'], - ['XCUITest Real Devices (iOS)', 'ios-xcuitest-real-devices.md'], - // ['UIAutomation (iOS)', 'ios-uiautomation.md'], - // ['UIAutomation Safari Launcher (iOS)', 'ios-uiautomation-safari-launcher.md'], - // ['UIAutomator (Android)', 'android-uiautomator.md'], - ['UIAutomator2 (Android)', 'android-uiautomator2.md'], - // ['Espresso (Android)', 'android-espresso.md'], - // ['Windows', 'windows.md'], - // ['Mac', 'mac.md'], - ]], - ['编写 & 运行测试', ['writing-running-appium', - ['运行测试', 'running-tests.md'], - ['预期功能', 'caps.md'], - ['--default-capabilities 标识', 'default-capabilities-arg.md'], - ['寻找元素', 'finding-elements.md'], - ['触控操作', 'touch-actions.md'], - ['命令行参数', 'server-args.md'], - ['Web / Web 视图', ['web', - ['自动化 Web 测试', 'mobile-web.md'], - ['自动化混合应用', 'hybrid.md'], - ['使用 Chromedriver', 'chromedriver.md'], - ]], - ['iOS', ['ios', - ['XCUITest 移动手势', 'ios-xctest-mobile-gestures.md'], - //['XCUITest 移动应用管理', 'ios-xctest-mobile-apps-management.md'], - //['iOS 粘贴板指南', 'ios-xctest-pasteboard.md'], - ['iOS 谓词(Predicate)指南', 'ios-predicate.md'], - ['iOS Touch ID 指南', 'ios-touch-id.md'], - //['iOS 安装证书', 'ios-xctest-install-certificate.md'], - //['tvOS 支持', 'ios-tvos.md'], - ]], - ['Android', ['android', - ['UiSelector 指南', 'uiautomator-uiselector.md'], - //['Espresso Datamatcher 指南', 'espresso-datamatcher-selector.md'], - ['Android 代码覆盖指南', 'android-coverage.md'], - //['Activities 启动障碍排斥指南', 'activity-startup.md'], - //['如何在远程设备上执行 Shell 命令', 'android-shell.md'], - //['如何模拟生成 IME 行为', 'android-ime.md'], - //['如何测试 Android 应用程序包', 'android-appbundle.md'] - ]], - ['其他', ['other', - ['重置策略', 'reset-strategies.md'], - ['网络连接指南', 'network-connection.md'], - ['在 Appium 中使用 Unicode', 'unicode.md'], - //['故障排除', 'troubleshooting.md'] - ]], - ]], - ['进阶', ['advanced-concepts', - ['迁移至 Appium 2.x', 'migrating-to-appium-2.0.md'], - //['定位图像中的元素', 'image-elements.md'], - //['用于定位元素的插件', 'element-finding-plugins.md'], - ['迁移到 XCUITest', 'migrating-to-xcuitest.md'], - ['在 Appium 中使用 Selenium Grid', 'grid.md'], - ['跨域 iframes', 'cross-domain-iframes.md'], - ['使用自定义 WDA 服务器', 'wda-custom-server.md'], - //['使用指定版本的 Xcode 运行', 'multiple-xcode-versions.md'], - ['Event Timings API', 'event-timings.md'], - ['并行测试的设置', 'parallel-tests.md'], - ['Settings API', 'settings.md'], - //['内存转储', 'memory-collection.md']] - ]], - ['捐赠', ['contributing-to-appium', - ['从源代码运行 Appium', 'appium-from-source.md'], - ['开发者概述', 'developers-overview.md'], - //['标准开发命令', 'dev-tools.md'], - ['Appium 风格指南', 'style-guide.md'], - ['如何编写文档', 'how-to-write-docs.md'], - ['Appium 包结构', 'appium-packages.md'], - ['鸣谢', 'credits.md']]] + [ + '关于', + [ + 'about-appium', + ['简介', 'intro.md'], + ['Appium 客户端', 'appium-clients.md'], + // ['入门指南', 'getting-started.md'], + ['已支持的平台', 'platform-support.md'], + // ['API 文档', 'api.md'], + ], + ], + [ + '驱动程序', + [ + 'drivers', + ['Driver CLI', 'driver-cli.md'], + ['XCUITest (iOS)', 'ios-xcuitest.md'], + ['XCUITest Real Devices (iOS)', 'ios-xcuitest-real-devices.md'], + // ['UIAutomation (iOS)', 'ios-uiautomation.md'], + // ['UIAutomation Safari Launcher (iOS)', 'ios-uiautomation-safari-launcher.md'], + // ['UIAutomator (Android)', 'android-uiautomator.md'], + ['UIAutomator2 (Android)', 'android-uiautomator2.md'], + // ['Espresso (Android)', 'android-espresso.md'], + // ['Windows', 'windows.md'], + // ['Mac', 'mac.md'], + ], + ], + [ + '编写 & 运行测试', + [ + 'writing-running-appium', + ['运行测试', 'running-tests.md'], + ['预期功能', 'caps.md'], + ['--default-capabilities 标识', 'default-capabilities-arg.md'], + ['寻找元素', 'finding-elements.md'], + ['触控操作', 'touch-actions.md'], + ['命令行参数', 'server-args.md'], + [ + 'Web / Web 视图', + [ + 'web', + ['自动化 Web 测试', 'mobile-web.md'], + ['自动化混合应用', 'hybrid.md'], + ['使用 Chromedriver', 'chromedriver.md'], + ], + ], + [ + 'iOS', + [ + 'ios', + ['XCUITest 移动手势', 'ios-xctest-mobile-gestures.md'], + //['XCUITest 移动应用管理', 'ios-xctest-mobile-apps-management.md'], + //['iOS 粘贴板指南', 'ios-xctest-pasteboard.md'], + ['iOS 谓词(Predicate)指南', 'ios-predicate.md'], + ['iOS Touch ID 指南', 'ios-touch-id.md'], + //['iOS 安装证书', 'ios-xctest-install-certificate.md'], + //['tvOS 支持', 'ios-tvos.md'], + ], + ], + [ + 'Android', + [ + 'android', + ['UiSelector 指南', 'uiautomator-uiselector.md'], + //['Espresso Datamatcher 指南', 'espresso-datamatcher-selector.md'], + ['Android 代码覆盖指南', 'android-coverage.md'], + //['Activities 启动障碍排斥指南', 'activity-startup.md'], + //['如何在远程设备上执行 Shell 命令', 'android-shell.md'], + //['如何模拟生成 IME 行为', 'android-ime.md'], + //['如何测试 Android 应用程序包', 'android-appbundle.md'] + ], + ], + [ + '其他', + [ + 'other', + ['重置策略', 'reset-strategies.md'], + ['网络连接指南', 'network-connection.md'], + ['在 Appium 中使用 Unicode', 'unicode.md'], + //['故障排除', 'troubleshooting.md'] + ], + ], + ], + ], + [ + '进阶', + [ + 'advanced-concepts', + ['迁移至 Appium 2.x', 'migrating-to-appium-2.0.md'], + //['定位图像中的元素', 'image-elements.md'], + //['用于定位元素的插件', 'element-finding-plugins.md'], + ['迁移到 XCUITest', 'migrating-to-xcuitest.md'], + ['在 Appium 中使用 Selenium Grid', 'grid.md'], + ['跨域 iframes', 'cross-domain-iframes.md'], + ['使用自定义 WDA 服务器', 'wda-custom-server.md'], + //['使用指定版本的 Xcode 运行', 'multiple-xcode-versions.md'], + ['Event Timings API', 'event-timings.md'], + ['并行测试的设置', 'parallel-tests.md'], + ['Settings API', 'settings.md'], + //['内存转储', 'memory-collection.md']] + ], + ], + [ + '捐赠', + [ + 'contributing-to-appium', + ['从源代码运行 Appium', 'appium-from-source.md'], + ['开发者概述', 'developers-overview.md'], + //['标准开发命令', 'dev-tools.md'], + ['Appium 风格指南', 'style-guide.md'], + ['如何编写文档', 'how-to-write-docs.md'], + ['Appium 包结构', 'appium-packages.md'], + ['鸣谢', 'credits.md'], + ], + ], ], ja: [ ['ホーム', 'about-appium/intro.md'], - ['概要', ['about-appium', - ['イントロ', 'intro.md'], - ['Appium クライアント', 'appium-clients.md'], - ['はじめに', 'getting-started.md'], - ['サポートプラットフォーム', 'platform-support.md'], - ]] - ] + [ + '概要', + [ + 'about-appium', + ['イントロ', 'intro.md'], + ['Appium クライアント', 'appium-clients.md'], + ['はじめに', 'getting-started.md'], + ['サポートプラットフォーム', 'platform-support.md'], + ], + ], + ], }; diff --git a/package.json b/package.json index 6d31a90d3..3e378c4c4 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,8 @@ }, "prettier": { "bracketSpacing": false, - "singleQuote": true + "singleQuote": true, + "printWidth": 100 }, "dependencies": { "@appium/base-driver": "file:packages/base-driver", diff --git a/packages/appium/docs/assets/stylesheets/extra.css b/packages/appium/docs/assets/stylesheets/extra.css index dfa080361..2fed4b210 100644 --- a/packages/appium/docs/assets/stylesheets/extra.css +++ b/packages/appium/docs/assets/stylesheets/extra.css @@ -1,6 +1,6 @@ .md-source__fact--version { display: none; } -.md-source__fact:nth-child(1n+2):before { +.md-source__fact:nth-child(1n + 2):before { margin-left: 0 !important; } diff --git a/packages/appium/docs/scripts/build-docs.js b/packages/appium/docs/scripts/build-docs.js index 5accb8bf2..f9e21441b 100644 --- a/packages/appium/docs/scripts/build-docs.js +++ b/packages/appium/docs/scripts/build-docs.js @@ -2,13 +2,20 @@ /* eslint-disable promise/prefer-await-to-callbacks */ /* eslint-disable promise/prefer-await-to-then */ -const { Mike } = require('@appium/docutils'); -const { log, LANGS, DOCS_DIR, DOCS_BRANCH, DOCS_PREFIX, - DOCS_REMOTE, LATEST_ALIAS } = require('./utils'); +const {Mike} = require('@appium/docutils'); +const { + log, + LANGS, + DOCS_DIR, + DOCS_BRANCH, + DOCS_PREFIX, + DOCS_REMOTE, + LATEST_ALIAS, +} = require('./utils'); const copyAssets = require('./copy-assets'); const path = require('path'); const semver = require('semver'); -const { version } = require('../../package.json'); +const {version} = require('../../package.json'); const branch = process.env.APPIUM_DOCS_BRANCH || DOCS_BRANCH; const prefix = process.env.APPIUM_DOCS_PREFIX || DOCS_PREFIX; @@ -16,7 +23,7 @@ const remote = process.env.APPIUM_DOCS_PREFIX || DOCS_REMOTE; const shouldPush = !!process.env.APPIUM_DOCS_PUBLISH; -async function main () { +async function main() { log.info(`Building Appium docs and committing to ${DOCS_BRANCH}`); await copyAssets(); @@ -27,7 +34,12 @@ async function main () { for (const lang of LANGS) { log.info(`Building docs for language '${lang}' and version ${majMinVer}`); const configFile = path.join(DOCS_DIR, `mkdocs-${lang}.yml`); - const m = new Mike({branch, prefix: path.join(prefix, lang), remote, configFile}); + const m = new Mike({ + branch, + prefix: path.join(prefix, lang), + remote, + configFile, + }); const docsAlreadyExisted = (await m.list()).length >= 1; @@ -36,7 +48,7 @@ async function main () { alias: LATEST_ALIAS, shouldRebase: shouldPush, shouldPush, - commit: `docs(appium): auto-build docs for appium@${majMinVer}, language ${lang}` + commit: `docs(appium): auto-build docs for appium@${majMinVer}, language ${lang}`, }; await m.deploy(deployOpts); diff --git a/packages/appium/docs/scripts/copy-assets.js b/packages/appium/docs/scripts/copy-assets.js index 39d92ee51..1b133d7c6 100644 --- a/packages/appium/docs/scripts/copy-assets.js +++ b/packages/appium/docs/scripts/copy-assets.js @@ -2,11 +2,11 @@ /* eslint-disable promise/prefer-await-to-callbacks */ /* eslint-disable promise/prefer-await-to-then */ -const { fs } = require('@appium/support'); -const { log, LANGS, DOCS_DIR, ASSETS_DIR } = require('./utils'); +const {fs} = require('@appium/support'); +const {log, LANGS, DOCS_DIR, ASSETS_DIR} = require('./utils'); const path = require('path'); -async function main () { +async function main() { log.info('Copying generic assets to docs language dirs'); for (const lang of LANGS) { diff --git a/packages/appium/docs/scripts/utils.js b/packages/appium/docs/scripts/utils.js index 98510f75f..115d474a4 100644 --- a/packages/appium/docs/scripts/utils.js +++ b/packages/appium/docs/scripts/utils.js @@ -1,6 +1,6 @@ // for simplicity this file is not transpiled and is run directly via an npm script // -const { logger } = require('@appium/support'); +const {logger} = require('@appium/support'); const path = require('path'); const log = logger.getLogger('Docs'); diff --git a/packages/appium/jsdoc-config.js b/packages/appium/jsdoc-config.js index ad4926972..d9c32a351 100644 --- a/packages/appium/jsdoc-config.js +++ b/packages/appium/jsdoc-config.js @@ -1,4 +1,4 @@ -const { baseConfig } = require('@appium/docutils'); +const {baseConfig} = require('@appium/docutils'); const lang = process.env.APPIUM_DOCS_LANG || 'en'; diff --git a/packages/appium/lib/appium.js b/packages/appium/lib/appium.js index 2cc329f11..63f50176d 100644 --- a/packages/appium/lib/appium.js +++ b/packages/appium/lib/appium.js @@ -115,9 +115,7 @@ class AppiumDriver extends DriverCore { */ get log() { if (!this._log) { - const instanceName = `${this.constructor.name}@${node - .getObjectId(this) - .substring(0, 4)}`; + const instanceName = `${this.constructor.name}@${node.getObjectId(this).substring(0, 4)}`; this._log = logger.getLogger(instanceName); } return this._log; @@ -191,9 +189,7 @@ class AppiumDriver extends DriverCore { const defaults = getDefaultsForExtension(extType, extName); const cliArgs = _.isEmpty(defaults) ? allCliArgsForExt - : _.omitBy(allCliArgsForExt, (value, key) => - _.isEqual(defaults[key], value) - ); + : _.omitBy(allCliArgsForExt, (value, key) => _.isEqual(defaults[key], value)); if (!_.isEmpty(cliArgs)) { extInstance.cliArgs = cliArgs; } @@ -236,14 +232,10 @@ class AppiumDriver extends DriverCore { defaultCapabilities ); - const { - desiredCaps, - processedJsonwpCapabilities, - processedW3CCapabilities, - } = /** @type {import('./utils').ParsedDriverCaps} */ (parsedCaps); + const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities} = + /** @type {import('./utils').ParsedDriverCaps} */ (parsedCaps); protocol = parsedCaps.protocol; - const error = /** @type {import('./utils').InvalidCaps} */ (parsedCaps) - .error; + const error = /** @type {import('./utils').InvalidCaps} */ (parsedCaps).error; // If the parsing of the caps produced an error, throw it in here if (error) { throw error; @@ -254,11 +246,7 @@ class AppiumDriver extends DriverCore { version: driverVersion, driverName, } = this.driverConfig.findMatchingDriver(desiredCaps); - this.printNewSessionAnnouncement( - InnerDriver.name, - driverVersion, - InnerDriver.baseVersion - ); + this.printNewSessionAnnouncement(InnerDriver.name, driverVersion, InnerDriver.baseVersion); if (this.args.sessionOverride) { await this.deleteAllSessions(); @@ -314,14 +302,12 @@ class AppiumDriver extends DriverCore { driverInstance.serverPath = this.args.basePath; try { - runningDriversData = - (await this.curSessionDataForDriver(InnerDriver)) ?? []; + runningDriversData = (await this.curSessionDataForDriver(InnerDriver)) ?? []; } catch (e) { throw new errors.SessionNotCreatedError(e.message); } await pendingDriversGuard.acquire(AppiumDriver.name, () => { - this.pendingDrivers[InnerDriver.name] = - this.pendingDrivers[InnerDriver.name] || []; + this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || []; otherPendingDriversData = _.compact( this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData) ); @@ -360,10 +346,7 @@ class AppiumDriver extends DriverCore { JSON.stringify(w3cSettings) ); await driverInstance.updateSettings(w3cSettings); - } else if ( - driverInstance.isMjsonwpProtocol() && - !_.isEmpty(jwpSettings) - ) { + } else if (driverInstance.isMjsonwpProtocol() && !_.isEmpty(jwpSettings)) { this.log.info( `Applying the initial values to Appium settings parsed from MJSONWP caps: ` + JSON.stringify(jwpSettings) @@ -401,16 +384,12 @@ class AppiumDriver extends DriverCore { ); } } else { - this.log.debug( - `Plugin ${plugin.name} does not define an unexpected shutdown handler` - ); + this.log.debug(`Plugin ${plugin.name} does not define an unexpected shutdown handler`); } } } - this.log.info( - `Removing session '${innerSessionId}' from our master session list` - ); + this.log.info(`Removing session '${innerSessionId}' from our master session list`); delete this.sessions[innerSessionId]; delete this.sessionPlugins[innerSessionId]; }; @@ -455,33 +434,26 @@ class AppiumDriver extends DriverCore { let protocol; try { let otherSessionsData; - const dstSession = await sessionsListGuard.acquire( - AppiumDriver.name, - () => { - if (!this.sessions[sessionId]) { - return; - } - const curConstructorName = this.sessions[sessionId].constructor.name; - otherSessionsData = _.toPairs(this.sessions) - .filter( - ([key, value]) => - value.constructor.name === curConstructorName && - key !== sessionId - ) - .map(([, value]) => value.driverData); - const dstSession = this.sessions[sessionId]; - protocol = dstSession.protocol; - this.log.info( - `Removing session ${sessionId} from our master session list` - ); - // regardless of whether the deleteSession completes successfully or not - // make the session unavailable, because who knows what state it might - // be in otherwise - delete this.sessions[sessionId]; - delete this.sessionPlugins[sessionId]; - return dstSession; + const dstSession = await sessionsListGuard.acquire(AppiumDriver.name, () => { + if (!this.sessions[sessionId]) { + return; } - ); + const curConstructorName = this.sessions[sessionId].constructor.name; + otherSessionsData = _.toPairs(this.sessions) + .filter( + ([key, value]) => value.constructor.name === curConstructorName && key !== sessionId + ) + .map(([, value]) => value.driverData); + const dstSession = this.sessions[sessionId]; + protocol = dstSession.protocol; + this.log.info(`Removing session ${sessionId} from our master session list`); + // regardless of whether the deleteSession completes successfully or not + // make the session unavailable, because who knows what state it might + // be in otherwise + delete this.sessions[sessionId]; + delete this.sessionPlugins[sessionId]; + return dstSession; + }); // this may not be correct, but if `dstSession` was falsy, the call to `deleteSession()` would // throw anyway. if (!dstSession) { @@ -508,9 +480,7 @@ class AppiumDriver extends DriverCore { } const {force = false, reason} = opts; - this.log.debug( - `Cleaning up ${util.pluralize('active session', sessionsCount, true)}` - ); + this.log.debug(`Cleaning up ${util.pluralize('active session', sessionsCount, true)}`); const cleanupPromises = force ? _.values(this.sessions).map((drv) => drv.startUnexpectedShutdown(reason && new Error(reason)) @@ -639,10 +609,7 @@ class AppiumDriver extends DriverCore { // if we're running with plugins, make sure we log that the default behavior is actually // happening so we can tell when the plugin call chain is unwrapping to the default behavior // if that's what happens - plugins.length && - this.log.info( - `Executing default handling behavior for command '${cmd}'` - ); + plugins.length && this.log.info(`Executing default handling behavior for command '${cmd}'`); // if we make it here, we know that the default behavior is handled cmdHandledBy.default = true; @@ -669,11 +636,7 @@ class AppiumDriver extends DriverCore { if (isUmbrellaCmd) { // some commands, like deleteSession, we want to make sure to handle on *this* driver, // not the platform driver - return await BaseDriver.prototype.executeCommand.call( - this, - cmd, - ...args - ); + return await BaseDriver.prototype.executeCommand.call(this, cmd, ...args); } // here we know that we are executing a session command, and have a valid session driver @@ -698,11 +661,7 @@ class AppiumDriver extends DriverCore { // And finally, if the command was createSession, we want to migrate any plugins which were // previously sessionless to use the new sessionId, so that plugins can share state between // their createSession method and other instance methods - if ( - cmd === CREATE_SESSION_COMMAND && - this.sessionlessPlugins.length && - !res.error - ) { + if (cmd === CREATE_SESSION_COMMAND && this.sessionlessPlugins.length && !res.error) { const sessionId = _.first(res.value); this.log.info( `Promoting ${this.sessionlessPlugins.length} sessionless plugins to be attached ` + @@ -717,9 +676,7 @@ class AppiumDriver extends DriverCore { wrapCommandWithPlugins({driver, cmd, args, next, cmdHandledBy, plugins}) { plugins.length && - this.log.info( - `Plugins which can handle cmd '${cmd}': ${plugins.map((p) => p.name)}` - ); + this.log.info(`Plugins which can handle cmd '${cmd}': ${plugins.map((p) => p.name)}`); // now we can go through each plugin and wrap `next` around its own handler, passing the *old* // next in so that it can call it if it wants to @@ -755,15 +712,11 @@ class AppiumDriver extends DriverCore { // interact well together, and it would be hard to debug otherwise without this kind of // message). const didHandle = Object.keys(cmdHandledBy).filter((k) => cmdHandledBy[k]); - const didntHandle = Object.keys(cmdHandledBy).filter( - (k) => !cmdHandledBy[k] - ); + const didntHandle = Object.keys(cmdHandledBy).filter((k) => !cmdHandledBy[k]); if (didntHandle.length > 0) { this.log.info( `Command '${cmd}' was *not* handled by the following behaviours or plugins, even ` + - `though they were registered to handle it: ${JSON.stringify( - didntHandle - )}. The ` + + `though they were registered to handle it: ${JSON.stringify(didntHandle)}. The ` + `command *was* handled by these: ${JSON.stringify(didHandle)}.` ); } @@ -797,11 +750,7 @@ class AppiumDriver extends DriverCore { proxyActive(sessionId) { const dstSession = this.sessions[sessionId]; - return ( - dstSession && - _.isFunction(dstSession.proxyActive) && - dstSession.proxyActive(sessionId) - ); + return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId); } getProxyAvoidList(sessionId) { diff --git a/packages/appium/lib/cli/args.js b/packages/appium/lib/cli/args.js index 41e47dd88..728613f51 100644 --- a/packages/appium/lib/cli/args.js +++ b/packages/appium/lib/cli/args.js @@ -147,8 +147,7 @@ function makeUninstallArgs(type) { { type: 'str', help: - 'Name of the driver to uninstall, for example: ' + type === - DRIVER_TYPE + 'Name of the driver to uninstall, for example: ' + type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE, }, @@ -205,8 +204,7 @@ function makeRunArgs(type) { { type: 'str', help: - `Name of the ${type} to run a script from, for example: ` + type === - DRIVER_TYPE + `Name of the ${type} to run a script from, for example: ` + type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE, }, diff --git a/packages/appium/lib/cli/driver-command.js b/packages/appium/lib/cli/driver-command.js index eeb53cbd5..78e8204dd 100644 --- a/packages/appium/lib/cli/driver-command.js +++ b/packages/appium/lib/cli/driver-command.js @@ -3,12 +3,7 @@ import ExtensionCommand from './extension-command'; import {KNOWN_DRIVERS} from '../constants'; import '@colors/colors'; -const REQ_DRIVER_FIELDS = [ - 'driverName', - 'automationName', - 'platformNames', - 'mainClass', -]; +const REQ_DRIVER_FIELDS = ['driverName', 'automationName', 'platformNames', 'mainClass']; /** * @extends {ExtensionCommand} diff --git a/packages/appium/lib/cli/extension-command.js b/packages/appium/lib/cli/extension-command.js index e9ec08adf..a94b84601 100644 --- a/packages/appium/lib/cli/extension-command.js +++ b/packages/appium/lib/cli/extension-command.js @@ -84,9 +84,7 @@ class ExtensionCommand { * @return {Promise} map of extension names to extension data */ async list({showInstalled, showUpdates}) { - const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${ - this.type - }s`; + const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s`; const installedNames = Object.keys(this.config.installedExtensions); const knownNames = Object.keys(this.knownExtensions); const exts = [...installedNames, ...knownNames].reduce( @@ -124,8 +122,7 @@ class ExtensionCommand { const updates = await this.checkForExtensionUpdate(ext); data.updateVersion = updates.safeUpdate; data.unsafeUpdateVersion = updates.unsafeUpdate; - data.upToDate = - updates.safeUpdate === null && updates.unsafeUpdate === null; + data.upToDate = updates.safeUpdate === null && updates.unsafeUpdate === null; } }); @@ -143,14 +140,8 @@ class ExtensionCommand { let upToDateTxt = ''; let unsafeUpdateTxt = ''; if (data.installed) { - const { - installType, - installSpec, - updateVersion, - unsafeUpdateVersion, - version, - upToDate, - } = data; + const {installType, installSpec, updateVersion, unsafeUpdateVersion, version, upToDate} = + data; let typeTxt; switch (installType) { case INSTALL_TYPE_GIT: @@ -163,9 +154,7 @@ class ExtensionCommand { default: typeTxt = '(NPM)'; } - installTxt = `@${version.yellow} ${ - ('[installed ' + typeTxt + ']').green - }`; + installTxt = `@${version.yellow} ${('[installed ' + typeTxt + ']').green}`; if (showUpdates) { if (updateVersion) { @@ -175,15 +164,12 @@ class ExtensionCommand { upToDateTxt = ` [Up to date]`.green; } if (unsafeUpdateVersion) { - unsafeUpdateTxt = - ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan; + unsafeUpdateTxt = ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan; } } } - console.log( - `- ${name.yellow}${installTxt}${updateTxt}${upToDateTxt}${unsafeUpdateTxt}` - ); + console.log(`- ${name.yellow}${installTxt}${updateTxt}${upToDateTxt}${unsafeUpdateTxt}`); } return listData; @@ -199,22 +185,12 @@ class ExtensionCommand { /** @type {ExtensionFields} */ let extData; - if ( - packageName && - [INSTALL_TYPE_LOCAL, INSTALL_TYPE_NPM].includes(installType) - ) { - throw new Error( - `When using --source=${installType}, cannot also use --package` - ); + if (packageName && [INSTALL_TYPE_LOCAL, INSTALL_TYPE_NPM].includes(installType)) { + throw new Error(`When using --source=${installType}, cannot also use --package`); } - if ( - !packageName && - [INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB].includes(installType) - ) { - throw new Error( - `When using --source=${installType}, must also use --package` - ); + if (!packageName && [INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB].includes(installType)) { + throw new Error(`When using --source=${installType}, must also use --package`); } if (installType === INSTALL_TYPE_GITHUB) { @@ -239,9 +215,7 @@ class ExtensionCommand { } else { let pkgName, pkgVer; if (installType === INSTALL_TYPE_LOCAL) { - pkgName = path.isAbsolute(installSpec) - ? installSpec - : path.resolve(installSpec); + pkgName = path.isAbsolute(installSpec) ? installSpec : path.resolve(installSpec); } else { // at this point we have either an npm package or an appium verified extension // name or a local path. both of which will be installed via npm. @@ -315,8 +289,7 @@ class ExtensionCommand { */ async installViaNpm({installSpec, pkgName, pkgVer}) { const npmSpec = `${pkgName}${pkgVer ? '@' + pkgVer : ''}`; - const specMsg = - npmSpec === installSpec ? '' : ` using NPM install spec '${npmSpec}'`; + const specMsg = npmSpec === installSpec ? '' : ` using NPM install spec '${npmSpec}'`; const msg = `Installing '${installSpec}'${specMsg}`; try { const pkgJsonData = await spinWith( @@ -329,9 +302,7 @@ class ExtensionCommand { ); return this.getExtensionFields(pkgJsonData, installSpec); } catch (err) { - throw new Error( - `Encountered an error when installing package: ${err.message}` - ); + throw new Error(`Encountered an error when installing package: ${err.message}`); } } @@ -396,9 +367,7 @@ class ExtensionCommand { */ async _uninstall({installSpec}) { if (!this.config.isInstalled(installSpec)) { - throw new Error( - `Can't uninstall ${this.type} '${installSpec}'; it is not installed` - ); + throw new Error(`Can't uninstall ${this.type} '${installSpec}'; it is not installed`); } const installPath = this.config.getInstallPath(installSpec); try { @@ -406,10 +375,7 @@ class ExtensionCommand { } finally { await this.config.removeExtension(installSpec); } - log( - this.isJsonOutput, - `Successfully uninstalled ${this.type} '${installSpec}'`.green - ); + log(this.isJsonOutput, `Successfully uninstalled ${this.type} '${installSpec}'`.green); return this.config.installedExtensions; } @@ -423,9 +389,7 @@ class ExtensionCommand { const shouldUpdateAll = installSpec === UPDATE_ALL; // if we're specifically requesting an update for an extension, make sure it's installed if (!shouldUpdateAll && !this.config.isInstalled(installSpec)) { - throw new Error( - `The ${this.type} '${installSpec}' was not installed, so can't be updated` - ); + throw new Error(`The ${this.type} '${installSpec}' was not installed, so can't be updated`); } const extsToUpdate = shouldUpdateAll ? Object.keys(this.config.installedExtensions) @@ -442,18 +406,11 @@ class ExtensionCommand { for (const e of extsToUpdate) { try { - await spinWith( - this.isJsonOutput, - `Checking if ${this.type} '${e}' is updatable`, - () => { - if ( - this.config.installedExtensions[e].installType !== - INSTALL_TYPE_NPM - ) { - throw new NotUpdatableError(); - } + await spinWith(this.isJsonOutput, `Checking if ${this.type} '${e}' is updatable`, () => { + if (this.config.installedExtensions[e].installType !== INSTALL_TYPE_NPM) { + throw new NotUpdatableError(); } - ); + }); const update = await spinWith( this.isJsonOutput, `Checking if ${this.type} '${e}' needs an update`, @@ -472,10 +429,7 @@ class ExtensionCommand { `breaking changes. If you want to apply this update, re-run with --unsafe` ); } - const updateVer = - unsafe && update.unsafeUpdate - ? update.unsafeUpdate - : update.safeUpdate; + const updateVer = unsafe && update.unsafeUpdate ? update.unsafeUpdate : update.safeUpdate; await spinWith( this.isJsonOutput, `Updating driver '${e}' from ${update.current} to ${updateVer}`, @@ -489,17 +443,13 @@ class ExtensionCommand { log(this.isJsonOutput, 'Update report:'); for (const [e, update] of _.toPairs(updates)) { - log( - this.isJsonOutput, - `- ${this.type} ${e} updated: ${update.from} => ${update.to}`.green - ); + log(this.isJsonOutput, `- ${this.type} ${e} updated: ${update.from} => ${update.to}`.green); } for (const [e, err] of _.toPairs(errors)) { if (err instanceof NotUpdatableError) { log( this.isJsonOutput, - `- '${e}' was not installed via npm, so we could not check ` + - `for updates`.yellow + `- '${e}' was not installed via npm, so we could not check ` + `for updates`.yellow ); } else if (err instanceof NoUpdatesAvailableError) { log(this.isJsonOutput, `- '${e}' had no updates available`.yellow); @@ -524,10 +474,7 @@ class ExtensionCommand { // this is a helper method, 'ext' is assumed to already be installed here, and of the npm // install type const {version, pkgName} = this.config.installedExtensions[ext]; - let unsafeUpdate = await npm.getLatestVersion( - this.config.appiumHome, - pkgName - ); + let unsafeUpdate = await npm.getLatestVersion(this.config.appiumHome, pkgName); let safeUpdate = await npm.getLatestSafeUpgradeVersion( this.config.appiumHome, pkgName, diff --git a/packages/appium/lib/cli/extension.js b/packages/appium/lib/cli/extension.js index 595f1c83e..1ae9f1b48 100644 --- a/packages/appium/lib/cli/extension.js +++ b/packages/appium/lib/cli/extension.js @@ -28,9 +28,7 @@ async function runExtensionCommand(args, configObject) { const {extensionType: type} = configObject; const extCmd = args[`${type}Command`]; if (!extCmd) { - throw new TypeError( - `Cannot call ${type} command without a subcommand like 'install'` - ); + throw new TypeError(`Cannot call ${type} command without a subcommand like 'install'`); } let {json, suppressOutput} = args; if (suppressOutput) { @@ -39,9 +37,7 @@ async function runExtensionCommand(args, configObject) { const logFn = (msg) => log(json, msg); let config = configObject; config.log = logFn; - const CommandClass = /** @type {ExtCommand} */ ( - commandClasses[type] - ); + const CommandClass = /** @type {ExtCommand} */ (commandClasses[type]); const cmd = new CommandClass({config, json}); try { jsonResult = await cmd.execute(args); diff --git a/packages/appium/lib/cli/parser.js b/packages/appium/lib/cli/parser.js index ae73076f4..47dae56ee 100644 --- a/packages/appium/lib/cli/parser.js +++ b/packages/appium/lib/cli/parser.js @@ -12,15 +12,7 @@ import {getExtensionArgs, getServerArgs} from './args'; * will automatially inject the `server` subcommand. */ const NON_SERVER_ARGS = Object.freeze( - new Set([ - DRIVER_TYPE, - PLUGIN_TYPE, - SERVER_SUBCOMMAND, - '-h', - '--help', - '-v', - '--version', - ]) + new Set([DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND, '-h', '--help', '-v', '--version']) ); const version = fs.readPackageJsonFrom(rootDir).version; @@ -138,9 +130,7 @@ class ArgParser { args, (unpacked, value, key) => { if (!_.isUndefined(value) && hasArgSpec(key)) { - const {dest} = /** @type {import('../schema/arg-spec').ArgSpec} */ ( - getArgSpec(key) - ); + const {dest} = /** @type {import('../schema/arg-spec').ArgSpec} */ (getArgSpec(key)); _.set(unpacked, dest, value); } else { // this could be anything that _isn't_ a server arg diff --git a/packages/appium/lib/cli/plugin-command.js b/packages/appium/lib/cli/plugin-command.js index 6f2b32fea..fe73f1e4c 100644 --- a/packages/appium/lib/cli/plugin-command.js +++ b/packages/appium/lib/cli/plugin-command.js @@ -12,12 +12,12 @@ export default class PluginCommand extends ExtensionCommand { * * @param {import('./extension-command').ExtensionCommandOptions} opts */ - constructor ({config, json}) { + constructor({config, json}) { super({config, json}); this.knownExtensions = KNOWN_PLUGINS; } - async install ({plugin, installType, packageName}) { + async install({plugin, installType, packageName}) { return await super._install({ installSpec: plugin, installType, @@ -25,19 +25,19 @@ export default class PluginCommand extends ExtensionCommand { }); } - async uninstall ({plugin}) { + async uninstall({plugin}) { return await super._uninstall({installSpec: plugin}); } - async update ({plugin, unsafe}) { + async update({plugin, unsafe}) { return await super._update({installSpec: plugin, unsafe}); } - async run ({plugin, scriptName}) { + async run({plugin, scriptName}) { return await super._run({installSpec: plugin, scriptName}); } - getPostInstallText ({extName, extData}) { + getPostInstallText({extName, extData}) { return `Plugin ${extName}@${extData.version} successfully installed`.green; } @@ -51,7 +51,7 @@ export default class PluginCommand extends ExtensionCommand { * @param {string} installSpec * @returns {void} */ - validateExtensionFields (pluginMetadata, installSpec) { + validateExtensionFields(pluginMetadata, installSpec) { const missingFields = REQ_PLUGIN_FIELDS.reduce( (acc, field) => (pluginMetadata[field] ? acc : [...acc, field]), [] diff --git a/packages/appium/lib/cli/utils.js b/packages/appium/lib/cli/utils.js index 6d9c2133e..bdde3cc2c 100644 --- a/packages/appium/lib/cli/utils.js +++ b/packages/appium/lib/cli/utils.js @@ -9,7 +9,7 @@ const JSON_SPACES = 4; * @param {boolean} json - whether we should log json or text * @param {any} msg - error message, object, Error instance, etc. */ -function errAndQuit (json, msg) { +function errAndQuit(json, msg) { if (json) { console.log(JSON.stringify({error: `${msg}`}, null, JSON_SPACES)); } else { @@ -26,7 +26,7 @@ function errAndQuit (json, msg) { * @param {boolean} json - whether we are in json mode (and should therefore not log) * @param {string} msg - string to log */ -function log (json, msg) { +function log(json, msg) { !json && console.log(msg); } @@ -36,7 +36,7 @@ function log (json, msg) { * @param {string} msg - string to log * @param {function} fn - function to wrap with spinning */ -async function spinWith (json, msg, fn) { +async function spinWith(json, msg, fn) { if (json) { return await fn(); } @@ -53,17 +53,17 @@ async function spinWith (json, msg, fn) { } class RingBuffer { - constructor (size = 50) { + constructor(size = 50) { this.size = size; this.buffer = []; } - getBuff () { + getBuff() { return this.buffer; } - dequeue () { + dequeue() { this.buffer.shift(); } - enqueue (item) { + enqueue(item) { if (this.buffer.length >= this.size) { this.dequeue(); } @@ -71,10 +71,4 @@ class RingBuffer { } } -export { - errAndQuit, - log, - spinWith, - JSON_SPACES, - RingBuffer -}; +export {errAndQuit, log, spinWith, JSON_SPACES, RingBuffer}; diff --git a/packages/appium/lib/config-file.js b/packages/appium/lib/config-file.js index 66a4cda84..0cb1c1df5 100644 --- a/packages/appium/lib/config-file.js +++ b/packages/appium/lib/config-file.js @@ -1,15 +1,14 @@ - import betterAjvErrors from '@sidvind/better-ajv-errors'; -import { lilconfig } from 'lilconfig'; +import {lilconfig} from 'lilconfig'; import _ from 'lodash'; import yaml from 'yaml'; -import { getSchema, validate } from './schema/schema'; +import {getSchema, validate} from './schema/schema'; /** * lilconfig loader to handle `.yaml` files * @type {import('lilconfig').LoaderSync} */ -function yamlLoader (filepath, content) { +function yamlLoader(filepath, content) { return yaml.parse(content); } @@ -26,7 +25,7 @@ const rawConfig = new Map(); * If it weren't for this cache, this would be unnecessary. * @type {import('lilconfig').LoaderSync} */ -function jsonLoader (filepath, content) { +function jsonLoader(filepath, content) { rawConfig.set(filepath, content); return JSON.parse(content); } @@ -37,13 +36,15 @@ function jsonLoader (filepath, content) { * @param {string} filepath - Path to config file * @returns {Promise} */ -async function loadConfigFile (lc, filepath) { +async function loadConfigFile(lc, filepath) { try { // removing "await" will cause any rejection to _not_ be caught in this block! return await lc.load(filepath); - } catch (/** @type {unknown} */err) { - if (/** @type {NodeJS.ErrnoException} */(err).code === 'ENOENT') { - /** @type {NodeJS.ErrnoException} */(err).message = `Config file not found at user-provided path: ${filepath}`; + } catch (/** @type {unknown} */ err) { + if (/** @type {NodeJS.ErrnoException} */ (err).code === 'ENOENT') { + /** @type {NodeJS.ErrnoException} */ ( + err + ).message = `Config file not found at user-provided path: ${filepath}`; throw err; } else if (err instanceof SyntaxError) { // generally invalid JSON @@ -59,7 +60,7 @@ async function loadConfigFile (lc, filepath) { * @param {LilconfigAsyncSearcher} lc - lilconfig instance * @returns {Promise} */ -async function searchConfigFile (lc) { +async function searchConfigFile(lc) { return await lc.search(); } @@ -78,7 +79,7 @@ async function searchConfigFile (lc) { * @throws {TypeError} If `errors` is empty * @returns {string} */ -export function formatErrors (errors = [], config = {}, opts = {}) { +export function formatErrors(errors = [], config = {}, opts = {}) { if (errors && !errors.length) { throw new TypeError('Array of errors must be non-empty'); } @@ -97,7 +98,7 @@ export function formatErrors (errors = [], config = {}, opts = {}) { * @public * @returns {Promise} Contains config and filepath, if found, and any errors */ -export async function readConfigFile (filepath, opts = {}) { +export async function readConfigFile(filepath, opts = {}) { const lc = lilconfig('appium', { loaders: { '.yaml': yamlLoader, @@ -105,12 +106,10 @@ export async function readConfigFile (filepath, opts = {}) { '.json': jsonLoader, noExt: jsonLoader, }, - packageProp: 'appiumConfig' + packageProp: 'appiumConfig', }); - const result = filepath - ? await loadConfigFile(lc, filepath) - : await searchConfigFile(lc); + const result = filepath ? await loadConfigFile(lc, filepath) : await searchConfigFile(lc); if (result?.filepath && !result?.isEmpty) { const {pretty = true} = opts; @@ -124,15 +123,11 @@ export async function readConfigFile (filepath, opts = {}) { json: rawConfig.get(result.filepath), pretty, }); - configResult = reason - ? {...result, errors, reason} - : {...result, errors}; + configResult = reason ? {...result, errors, reason} : {...result, errors}; } // normalize (to camel case) all top-level property names of the config file - configResult.config = normalizeConfig( - /** @type {AppiumConfig} */ (configResult.config), - ); + configResult.config = normalizeConfig(/** @type {AppiumConfig} */ (configResult.config)); return configResult; } finally { @@ -148,7 +143,7 @@ export async function readConfigFile (filepath, opts = {}) { * @param {AppiumConfig} config - Configuration object * @returns {NormalizedAppiumConfig} New object with camel-cased keys (or `dest` keys). */ -export function normalizeConfig (config) { +export function normalizeConfig(config) { const schema = getSchema(); /** * @param {AppiumConfig} config @@ -159,8 +154,9 @@ export function normalizeConfig (config) { const normalize = (config, section) => { const obj = _.isUndefined(section) ? config : _.get(config, section, config); - const mappedObj = _.mapKeys(obj, (__, prop) => - schema.properties[prop]?.appiumCliDest ?? _.camelCase(prop), + const mappedObj = _.mapKeys( + obj, + (__, prop) => schema.properties[prop]?.appiumCliDest ?? _.camelCase(prop) ); return _.mapValues(mappedObj, (value, property) => { diff --git a/packages/appium/lib/config.js b/packages/appium/lib/config.js index a212e0459..658a6bfa2 100644 --- a/packages/appium/lib/config.js +++ b/packages/appium/lib/config.js @@ -25,9 +25,7 @@ const BUILD_INFO = { }; function getNodeVersion() { - return /** @type {import('semver').SemVer} */ ( - semver.coerce(process.version) - ); + return /** @type {import('semver').SemVer} */ (semver.coerce(process.version)); } async function updateBuildInfo(useGithubApiFallback = false) { @@ -96,13 +94,9 @@ async function getGitTimestamp(commitSha, useGithubApiFallback = false) { const gitRoot = await findGitRoot(); if (gitRoot) { try { - const {stdout} = await exec( - GIT_BINARY, - ['show', '-s', '--format=%ci', commitSha], - { - cwd: gitRoot, - } - ); + const {stdout} = await exec(GIT_BINARY, ['show', '-s', '--format=%ci', commitSha], { + cwd: gitRoot, + }); return stdout.trim(); } catch (ign) {} } @@ -145,9 +139,7 @@ function getBuildInfo() { function checkNodeOk() { const version = getNodeVersion(); if (!semver.satisfies(version, MIN_NODE_VERSION)) { - logger.errorAndThrow( - `Node version must be ${MIN_NODE_VERSION}. Currently ${version.version}` - ); + logger.errorAndThrow(`Node version must be ${MIN_NODE_VERSION}. Currently ${version.version}`); } } @@ -206,8 +198,7 @@ function getNonDefaultServerArgs(parsedArgs) { const defaultValueIsArray = /** @param {string} dest */ (dest) => _.isArray(defaultsFromSchema[dest]); - const argsValueIsArray = /** @param {string} dest */ (dest) => - _.isArray(args[dest].value); + const argsValueIsArray = /** @param {string} dest */ (dest) => _.isArray(args[dest].value); const arraysDiffer = /** @param {string} dest */ (dest) => _.gt(_.size(_.difference(args[dest].value, defaultsFromSchema[dest])), 0); @@ -220,10 +211,7 @@ function getNonDefaultServerArgs(parsedArgs) { // note that `_.overEvery` is like an "AND", and `_.overSome` is like an "OR" - const argValueNotArrayOrArraysDiffer = _.overSome([ - _.negate(argsValueIsArray), - arraysDiffer, - ]); + const argValueNotArrayOrArraysDiffer = _.overSome([_.negate(argsValueIsArray), arraysDiffer]); const defaultValueNotArrayAndValuesDiffer = _.overEvery([ _.negate(defaultValueIsArray), @@ -272,9 +260,7 @@ const compactConfig = _.partial( _.omitBy, _, (value, key) => - key === 'subcommand' || - _.isUndefined(value) || - (_.isObject(value) && _.isEmpty(value)) + key === 'subcommand' || _.isUndefined(value) || (_.isObject(value) && _.isEmpty(value)) ); /** @@ -288,12 +274,7 @@ const compactConfig = _.partial( * @param {Partial} defaults - Configuration defaults from schemas * @param {ParsedArgs} parsedArgs - Entire parsed args object */ -function showConfig( - nonDefaultPreConfigParsedArgs, - configResult, - defaults, - parsedArgs -) { +function showConfig(nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) { console.log('Appium Configuration\n'); console.log('from defaults:\n'); console.dir(compactConfig(defaults)); diff --git a/packages/appium/lib/constants.js b/packages/appium/lib/constants.js index d46b16ae1..4a8cddf3d 100644 --- a/packages/appium/lib/constants.js +++ b/packages/appium/lib/constants.js @@ -1,4 +1,3 @@ - import path from 'path'; /** @@ -30,7 +29,7 @@ export const KNOWN_PLUGINS = Object.freeze( images: '@appium/images-plugin', 'execute-driver': '@appium/execute-driver-plugin', 'relaxed-caps': '@appium/relaxed-caps-plugin', - }), + }) ); // This is a map of driver names to npm packages representing those drivers. @@ -50,26 +49,18 @@ export const KNOWN_DRIVERS = Object.freeze( flutter: 'appium-flutter-driver', safari: 'appium-safari-driver', gecko: 'appium-geckodriver', - }), + }) ); /** * Relative path to directory containing any Appium internal files */ -export const CACHE_DIR_RELATIVE_PATH = path.join( - 'node_modules', - '.cache', - 'appium', -); +export const CACHE_DIR_RELATIVE_PATH = path.join('node_modules', '.cache', 'appium'); /** * Relative path to hashfile (from `APPIUM_HOME`) of consuming project's `package.json` (if it exists) */ -export const PKG_HASHFILE_RELATIVE_PATH = path.join( - CACHE_DIR_RELATIVE_PATH, - 'package.hash', -); - +export const PKG_HASHFILE_RELATIVE_PATH = path.join(CACHE_DIR_RELATIVE_PATH, 'package.hash'); export const EXT_SUBCOMMAND_LIST = 'list'; export const EXT_SUBCOMMAND_INSTALL = 'install'; diff --git a/packages/appium/lib/extension/driver-config.js b/packages/appium/lib/extension/driver-config.js index 1d41a4878..38eb5d18e 100644 --- a/packages/appium/lib/extension/driver-config.js +++ b/packages/appium/lib/extension/driver-config.js @@ -192,8 +192,7 @@ export class DriverConfig extends ExtensionConfig { const drivers = this.installedExtensions; for (const [driverName, driverData] of _.toPairs(drivers)) { const {automationName, platformNames} = driverData; - const aNameMatches = - automationName.toLowerCase() === matchAutomationName.toLowerCase(); + const aNameMatches = automationName.toLowerCase() === matchAutomationName.toLowerCase(); const pNameMatches = _.includes( platformNames.map(_.toLower), matchPlatformName.toLowerCase() diff --git a/packages/appium/lib/extension/extension-config.js b/packages/appium/lib/extension/extension-config.js index 9ea51b92c..a2c378a39 100644 --- a/packages/appium/lib/extension/extension-config.js +++ b/packages/appium/lib/extension/extension-config.js @@ -71,12 +71,8 @@ export class ExtensionConfig { * @param {ExtRecord} exts - Extension data */ validate(exts) { - const foundProblems = - /** @type {Record,Problem[]>} */ ({}); - for (const [ - extName, - extData, - ] of /** @type {[ExtName, ExtManifest][]} */ ( + const foundProblems = /** @type {Record,Problem[]>} */ ({}); + for (const [extName, extData] of /** @type {[ExtName, ExtManifest][]} */ ( _.toPairs(exts) )) { foundProblems[extName] = [ @@ -94,13 +90,11 @@ export class ExtensionConfig { // remove this extension from the list since it's not valid delete exts[extName]; problemSummaries.push( - `${this.extensionType} ${extName} had errors and will not ` + - `be available. Errors:` + `${this.extensionType} ${extName} had errors and will not ` + `be available. Errors:` ); for (const problem of problems) { problemSummaries.push( - ` - ${problem.err} (Actual value: ` + - `${JSON.stringify(problem.val)})` + ` - ${problem.err} (Actual value: ` + `${JSON.stringify(problem.val)})` ); } } @@ -276,10 +270,7 @@ export class ExtensionConfig { } log.info(`Available ${this.configKey}:`); - for (const [ - extName, - extData, - ] of /** @type {[string, ExtManifest][]} */ ( + for (const [extName, extData] of /** @type {[string, ExtManifest][]} */ ( _.toPairs(this.installedExtensions) )) { log.info(` - ${this.extensionDesc(extName, extData)}`); @@ -303,11 +294,7 @@ export class ExtensionConfig { * @returns {string} */ getInstallPath(extName) { - return path.join( - this.appiumHome, - 'node_modules', - this.installedExtensions[extName].pkgName - ); + return path.join(this.appiumHome, 'node_modules', this.installedExtensions[extName].pkgName); } /** @@ -355,18 +342,13 @@ export class ExtensionConfig { } let moduleObject; if (_.isString(argSchemaPath)) { - const schemaPath = resolveFrom( - appiumHome, - path.join(pkgName, argSchemaPath) - ); + const schemaPath = resolveFrom(appiumHome, path.join(pkgName, argSchemaPath)); moduleObject = require(schemaPath); } else { moduleObject = argSchemaPath; } // this sucks. default exports should be destroyed - const schema = moduleObject.__esModule - ? moduleObject.default - : moduleObject; + const schema = moduleObject.__esModule ? moduleObject.default : moduleObject; registerSchema(extType, extName, schema); return schema; } @@ -399,13 +381,7 @@ export class ExtensionConfig { } } -export { - INSTALL_TYPE_NPM, - INSTALL_TYPE_GIT, - INSTALL_TYPE_LOCAL, - INSTALL_TYPE_GITHUB, - INSTALL_TYPES, -}; +export {INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_LOCAL, INSTALL_TYPE_GITHUB, INSTALL_TYPES}; /** * Config problem diff --git a/packages/appium/lib/extension/index.js b/packages/appium/lib/extension/index.js index c6cd5a7cf..3a709c7e8 100644 --- a/packages/appium/lib/extension/index.js +++ b/packages/appium/lib/extension/index.js @@ -20,11 +20,9 @@ export async function loadExtensions(appiumHome) { const manifest = Manifest.getInstance(appiumHome); const {drivers, plugins} = await manifest.read(); const driverConfig = - DriverConfig.getInstance(manifest) ?? - DriverConfig.create(manifest, {extData: drivers}); + DriverConfig.getInstance(manifest) ?? DriverConfig.create(manifest, {extData: drivers}); const pluginConfig = - PluginConfig.getInstance(manifest) ?? - PluginConfig.create(manifest, {extData: plugins}); + PluginConfig.getInstance(manifest) ?? PluginConfig.create(manifest, {extData: plugins}); return {driverConfig, pluginConfig}; } @@ -75,10 +73,7 @@ export function getActivePlugins(pluginConfig, usePlugins = []) { export function getActiveDrivers(driverConfig, useDrivers = []) { return _.compact( Object.keys(driverConfig.installedExtensions) - .filter( - (driverName) => - _.includes(useDrivers, driverName) || useDrivers.length === 0 - ) + .filter((driverName) => _.includes(useDrivers, driverName) || useDrivers.length === 0) .map((driverName) => { try { log.info(`Attempting to load driver ${driverName}...`); diff --git a/packages/appium/lib/extension/manifest.js b/packages/appium/lib/extension/manifest.js index 6ba67a3e1..118b00888 100644 --- a/packages/appium/lib/extension/manifest.js +++ b/packages/appium/lib/extension/manifest.js @@ -182,20 +182,14 @@ export class Manifest { const walkOpts = _.defaults({depthLimit}, DEFAULT_FIND_EXTENSIONS_OPTS); // this could be parallelized, but we can't use fs.walk as an async iterator let didChange = false; - for await (const {stats, path: filepath} of fs.walk( - this._appiumHome, - walkOpts - )) { + for await (const {stats, path: filepath} of fs.walk(this._appiumHome, walkOpts)) { if (filepath !== this._appiumHome && stats.isDirectory()) { try { const pkg = await env.readPackageInDir(filepath); if (pkg && isExtension(pkg)) { // it's possible that this extension already exists in the manifest, // so only update `didChange` if it's new. - const added = this.addExtensionFromPackage( - pkg, - path.join(filepath, 'package.json') - ); + const added = this.addExtensionFromPackage(pkg, path.join(filepath, 'package.json')); didChange = didChange || added; } } catch {} @@ -262,9 +256,7 @@ export class Manifest { return false; } else { throw new TypeError( - `The extension in ${path.dirname( - pkgPath - )} is neither a valid driver nor a valid plugin.` + `The extension in ${path.dirname(pkgPath)} is neither a valid driver nor a valid plugin.` ); } } @@ -419,11 +411,7 @@ export class Manifest { ); } try { - await fs.writeFile( - this._manifestPath, - YAML.stringify(this._data), - 'utf8' - ); + await fs.writeFile(this._manifestPath, YAML.stringify(this._data), 'utf8'); return true; } catch (err) { throw new Error( diff --git a/packages/appium/lib/extension/package-changed.js b/packages/appium/lib/extension/package-changed.js index 9d1a9ce30..5b17155df 100644 --- a/packages/appium/lib/extension/package-changed.js +++ b/packages/appium/lib/extension/package-changed.js @@ -1,8 +1,7 @@ - -import { fs } from '@appium/support'; -import { isPackageChanged } from 'package-changed'; +import {fs} from '@appium/support'; +import {isPackageChanged} from 'package-changed'; import path from 'path'; -import { PKG_HASHFILE_RELATIVE_PATH } from '../constants'; +import {PKG_HASHFILE_RELATIVE_PATH} from '../constants'; import log from '../logger'; /** @@ -14,7 +13,7 @@ import log from '../logger'; * @param {string} appiumHome * @returns {Promise} `true` if `package.json` `appiumHome` changed */ -export async function packageDidChange (appiumHome) { +export async function packageDidChange(appiumHome) { const hashFilename = path.join(appiumHome, PKG_HASHFILE_RELATIVE_PATH); // XXX: the types in `package-changed` seem to be wrong. @@ -35,7 +34,7 @@ export async function packageDidChange (appiumHome) { await fs.mkdirp(hashFilenameDir); } catch (err) { throw new Error( - `Appium could not create the directory for hash file: ${hashFilenameDir}. Original error: ${err.message}`, + `Appium could not create the directory for hash file: ${hashFilenameDir}. Original error: ${err.message}` ); } @@ -51,10 +50,12 @@ export async function packageDidChange (appiumHome) { if (isChanged) { try { writeHash(); - log.debug(`Updated hash of ${appiumHome}/package.json from: ${oldHash ?? '(none)'} to: ${hash}`); + log.debug( + `Updated hash of ${appiumHome}/package.json from: ${oldHash ?? '(none)'} to: ${hash}` + ); } catch (err) { throw new Error( - `Appium could not write hash file: ${hashFilenameDir}. Original error: ${err.message}`, + `Appium could not write hash file: ${hashFilenameDir}. Original error: ${err.message}` ); } } diff --git a/packages/appium/lib/extension/plugin-config.js b/packages/appium/lib/extension/plugin-config.js index 6865912c3..ba3031237 100644 --- a/packages/appium/lib/extension/plugin-config.js +++ b/packages/appium/lib/extension/plugin-config.js @@ -89,9 +89,7 @@ export class PluginConfig extends ExtensionConfig { } log.info(`Available plugins:`); - for (const [pluginName, pluginData] of _.toPairs( - this.installedExtensions - )) { + for (const [pluginName, pluginData] of _.toPairs(this.installedExtensions)) { const activeTxt = _.includes(activeNames, pluginName) ? ' (ACTIVE)' : ''; log.info(` - ${this.extensionDesc(pluginName, pluginData)}${activeTxt}`); } diff --git a/packages/appium/lib/grid-register.js b/packages/appium/lib/grid-register.js index 37c7e7db1..2b33a11a6 100644 --- a/packages/appium/lib/grid-register.js +++ b/packages/appium/lib/grid-register.js @@ -1,9 +1,8 @@ import axios from 'axios'; -import { fs } from '@appium/support'; +import {fs} from '@appium/support'; import logger from './logger'; import _ from 'lodash'; - const hubUri = (config) => { const protocol = config.hubProtocol || 'http'; return `${protocol}://${config.hubHost}:${config.hubPort}`; @@ -16,20 +15,24 @@ const hubUri = (config) => { * @param {number} [port] - Bind to this port * @param {string} [basePath] - Base path for the grid */ -async function registerNode (data, addr, port, basePath) { +async function registerNode(data, addr, port, basePath) { let configFilePath; if (_.isString(data)) { configFilePath = data; try { data = await fs.readFile(data, 'utf-8'); } catch (err) { - logger.error(`Unable to load node configuration file ${configFilePath} to register with grid: ${err.message}`); + logger.error( + `Unable to load node configuration file ${configFilePath} to register with grid: ${err.message}` + ); return; } try { data = JSON.parse(data); } catch (err) { - logger.errorAndThrow(`Syntax error in node configuration file ${configFilePath}: ${err.message}`); + logger.errorAndThrow( + `Syntax error in node configuration file ${configFilePath}: ${err.message}` + ); return; } } @@ -37,20 +40,21 @@ async function registerNode (data, addr, port, basePath) { postRequest(data, addr, port, basePath); } -async function registerToGrid (postOptions, configHolder) { +async function registerToGrid(postOptions, configHolder) { try { const {status} = await axios(postOptions); if (status !== 200) { throw new Error(`Request failed with code ${status}`); } - logger.debug(`Appium successfully registered with the the grid on ` + - hubUri(configHolder.configuration)); + logger.debug( + `Appium successfully registered with the the grid on ` + hubUri(configHolder.configuration) + ); } catch (err) { logger.error(`An attempt to register with the grid was unsuccessful: ${err.message}`); } } -function postRequest (configHolder, addr, port, basePath) { +function postRequest(configHolder, addr, port, basePath) { // Move Selenium 3 configuration properties to configuration object if (!_.has(configHolder, 'configuration')) { let configuration = {}; @@ -68,7 +72,11 @@ function postRequest (configHolder, addr, port, basePath) { // otherwise, we will take whatever the user setup // because we will always set localhost/127.0.0.1. this won't work if your // node and grid aren't in the same place - if (!configHolder.configuration.url || !configHolder.configuration.host || !configHolder.configuration.port) { + if ( + !configHolder.configuration.url || + !configHolder.configuration.host || + !configHolder.configuration.port + ) { configHolder.configuration.url = `http://${addr}:${port}${basePath}`; configHolder.configuration.host = addr; configHolder.configuration.port = port; @@ -92,26 +100,30 @@ function postRequest (configHolder, addr, port, basePath) { const registerCycleInterval = configHolder.configuration.registerCycle; if (isNaN(registerCycleInterval) || registerCycleInterval <= 0) { - logger.warn(`'registerCycle' is not a valid positive number. ` + - `No registration request will be sent to the grid.`); + logger.warn( + `'registerCycle' is not a valid positive number. ` + + `No registration request will be sent to the grid.` + ); return; } // initiate a new Thread let first = true; - logger.debug(`Starting auto register thread for the grid. ` + - `Will try to register every ${registerCycleInterval} ms.`); - setInterval(async function registerRetry () { + logger.debug( + `Starting auto register thread for the grid. ` + + `Will try to register every ${registerCycleInterval} ms.` + ); + setInterval(async function registerRetry() { if (first) { first = false; await registerToGrid(regRequest, configHolder); - } else if (!await isAlreadyRegistered(configHolder)) { + } else if (!(await isAlreadyRegistered(configHolder))) { // make the http POST to the grid for registration await registerToGrid(regRequest, configHolder); } }, registerCycleInterval); } -async function isAlreadyRegistered (configHolder) { +async function isAlreadyRegistered(configHolder) { //check if node is already registered const id = configHolder.configuration.id; try { @@ -132,5 +144,4 @@ async function isAlreadyRegistered (configHolder) { } } - export default registerNode; diff --git a/packages/appium/lib/logger.js b/packages/appium/lib/logger.js index 1ee371e13..799f884c9 100644 --- a/packages/appium/lib/logger.js +++ b/packages/appium/lib/logger.js @@ -1,5 +1,4 @@ -import { logger } from '@appium/support'; - +import {logger} from '@appium/support'; let log = logger.getLogger('Appium'); diff --git a/packages/appium/lib/logsink.js b/packages/appium/lib/logsink.js index ddca4eb31..915aa3cf4 100644 --- a/packages/appium/lib/logsink.js +++ b/packages/appium/lib/logsink.js @@ -1,9 +1,8 @@ import npmlog from 'npmlog'; -import { createLogger, format, transports } from 'winston'; -import { fs, logger } from '@appium/support'; +import {createLogger, format, transports} from 'winston'; +import {fs, logger} from '@appium/support'; import _ from 'lodash'; - // set up distributed logging before everything else logger.patchLogger(npmlog); global._global_npmlog = npmlog; @@ -39,16 +38,13 @@ let useLocalTimeZone = false; // add the timestamp in the correct format to the log info object const timestampFormat = format.timestamp({ - format () { + format() { let date = new Date(); if (useLocalTimeZone) { date = new Date(date.valueOf() - date.getTimezoneOffset() * 60000); } // '2012-11-04T14:51:06.157Z' -> '2012-11-04 14:51:06:157' - return date.toISOString() - .replace(/[TZ]/g, ' ') - .replace(/\./g, ':') - .trim(); + return date.toISOString().replace(/[TZ]/g, ' ').replace(/\./g, ':').trim(); }, }); @@ -58,14 +54,14 @@ const colorizeFormat = format.colorize({ }); // Strip the color marking within messages -const stripColorFormat = format(function stripColor (info) { +const stripColorFormat = format(function stripColor(info) { const code = /\u001b\[(\d+(;\d+)*)?m/g; // eslint-disable-line no-control-regex info.message = info.message.replace(code, ''); return info; })(); -function createConsoleTransport (args, logLvl) { - return new (transports.Console)({ +function createConsoleTransport(args, logLvl) { + return new transports.Console({ // `name` is unsupported per winston's type declarations // @ts-expect-error name: 'console', @@ -75,7 +71,7 @@ function createConsoleTransport (args, logLvl) { level: logLvl, stderrLevels: ['error'], format: format.combine( - format(function adjustDebug (info) { + format(function adjustDebug(info) { // prepend debug marker, and shift to `info` log level if (info.level === 'debug') { info.level = 'info'; @@ -85,15 +81,15 @@ function createConsoleTransport (args, logLvl) { })(), timestampFormat, args.logNoColors ? stripColorFormat : colorizeFormat, - format.printf(function printInfo (info) { + format.printf(function printInfo(info) { return `${args.logTimestamp ? `${info.timestamp} - ` : ''}${info.message}`; }) ), }); } -function createFileTransport (args, logLvl) { - return new (transports.File)({ +function createFileTransport(args, logLvl) { + return new transports.File({ // @ts-expect-error name: 'file', filename: args.logFile, @@ -105,14 +101,14 @@ function createFileTransport (args, logLvl) { format: format.combine( stripColorFormat, timestampFormat, - format.printf(function printInfo (info) { + format.printf(function printInfo(info) { return `${info.timestamp} ${info.message}`; }) - ) + ), }); } -function createHttpTransport (args, logLvl) { +function createHttpTransport(args, logLvl) { let host = '127.0.0.1'; let port = 9003; @@ -122,7 +118,7 @@ function createHttpTransport (args, logLvl) { port = parseInt(hostAndPort[1], 10); } - return new (transports.Http)({ + return new transports.Http({ // @ts-expect-error name: 'http', host, @@ -134,14 +130,14 @@ function createHttpTransport (args, logLvl) { level: logLvl, format: format.combine( stripColorFormat, - format.printf(function printInfo (info) { + format.printf(function printInfo(info) { return `${info.timestamp} ${info.message}`; }) ), }); } -async function createTransports (args) { +async function createTransports(args) { let transports = []; let consoleLogLevel = null; let fileLogLevel = null; @@ -169,8 +165,9 @@ async function createTransports (args) { transports.push(createFileTransport(args, fileLogLevel)); } catch (e) { // eslint-disable-next-line no-console - console.log(`Tried to attach logging to file '${args.logFile}' but an error ` + - `occurred: ${e.message}`); + console.log( + `Tried to attach logging to file '${args.logFile}' but an error ` + `occurred: ${e.message}` + ); } } @@ -179,15 +176,17 @@ async function createTransports (args) { transports.push(createHttpTransport(args, fileLogLevel)); } catch (e) { // eslint-disable-next-line no-console - console.log(`Tried to attach logging to Http at ${args.webhook} but ` + - `an error occurred: ${e.message}`); + console.log( + `Tried to attach logging to Http at ${args.webhook} but ` + + `an error occurred: ${e.message}` + ); } } return transports; } -async function init (args) { +async function init(args) { // set de facto param passed to timestamp function useLocalTimeZone = args.localTimezone; @@ -211,11 +210,10 @@ async function init (args) { if (args.logHandler && _.isFunction(args.logHandler)) { args.logHandler(logObj.level, msg); } - }); } -function clear () { +function clear() { if (log) { for (let transport of _.keys(log.transports)) { log.remove(transport); @@ -224,6 +222,5 @@ function clear () { npmlog.removeAllListeners('log'); } - -export { init, clear }; +export {init, clear}; export default init; diff --git a/packages/appium/lib/main.js b/packages/appium/lib/main.js index bd8f7c28d..4dcb4948b 100755 --- a/packages/appium/lib/main.js +++ b/packages/appium/lib/main.js @@ -3,10 +3,7 @@ import {init as logsinkInit} from './logsink'; // this import needs to come first since it sets up global npmlog import logger from './logger'; // logger needs to remain second // @ts-ignore -import { - routeConfiguringFunction as makeRouter, - server as baseServer, -} from '@appium/base-driver'; +import {routeConfiguringFunction as makeRouter, server as baseServer} from '@appium/base-driver'; import {logger as logFactory, util, env} from '@appium/support'; import {asyncify} from 'asyncbox'; import _ from 'lodash'; @@ -115,8 +112,7 @@ async function logStartupInfo(args) { * @returns {void} */ function logServerPort(address, port) { - let logMessage = - `Appium REST http interface listener started on ` + `${address}:${port}`; + let logMessage = `Appium REST http interface listener started on ` + `${address}:${port}`; logger.info(logMessage); } @@ -140,7 +136,7 @@ function getExtraMethodMap(driverClasses, pluginClasses) { return [...driverClasses, ...pluginClasses].reduce( (map, klass) => ({ ...map, - .../** @type {DriverClass} */ ((klass).newMethodMap ?? {}), + .../** @type {DriverClass} */ (klass.newMethodMap ?? {}), }), {} ); @@ -228,29 +224,19 @@ async function init(args) { const defaults = getDefaultsForSchema(false); /** @type {ParsedArgs} */ - const serverArgs = _.defaultsDeep( - preConfigArgs, - configResult.config?.server, - defaults - ); + const serverArgs = _.defaultsDeep(preConfigArgs, configResult.config?.server, defaults); if (preConfigArgs.showConfig) { - showConfig( - getNonDefaultServerArgs(preConfigArgs), - configResult, - defaults, - serverArgs - ); + showConfig(getNonDefaultServerArgs(preConfigArgs), configResult, defaults, serverArgs); return {}; } await logsinkInit(serverArgs); if (serverArgs.logFilters) { - const {issues, rules} = - await logFactory.loadSecureValuesPreprocessingRules( - serverArgs.logFilters - ); + const {issues, rules} = await logFactory.loadSecureValuesPreprocessingRules( + serverArgs.logFilters + ); if (!_.isEmpty(issues)) { throw new Error( `The log filtering rules config '${serverArgs.logFilters}' has issues: ` + @@ -263,11 +249,9 @@ async function init(args) { ); } else { logger.info( - `Loaded ${util.pluralize( - 'filtering rule', - rules.length, - true - )} from '${serverArgs.logFilters}'` + `Loaded ${util.pluralize('filtering rule', rules.length, true)} from '${ + serverArgs.logFilters + }'` ); } } @@ -294,8 +278,9 @@ async function init(args) { * @returns {Promise} */ async function main(args) { - const {appiumDriver, parsedArgs, pluginConfig, driverConfig} = - /** @type {ServerInitResult} */ (await init(args)); + const {appiumDriver, parsedArgs, pluginConfig, driverConfig} = /** @type {ServerInitResult} */ ( + await init(args) + ); if (!appiumDriver || !parsedArgs || !pluginConfig || !driverConfig) { // if this branch is taken, we've run a different subcommand, so there's nothing diff --git a/packages/appium/lib/schema/arg-spec.js b/packages/appium/lib/schema/arg-spec.js index 8ba23c431..c71d2eb5c 100644 --- a/packages/appium/lib/schema/arg-spec.js +++ b/packages/appium/lib/schema/arg-spec.js @@ -96,7 +96,7 @@ export class ArgSpec { * @param {string} name * @param {ArgSpecOptions} [opts] */ - constructor (name, {extType, extName, dest, defaultValue} = {}) { + constructor(name, {extType, extName, dest, defaultValue} = {}) { // we must normalize the extension name to fit into our convention for CLI // args. const arg = ArgSpec.toArg(name, extType, extName); @@ -107,8 +107,7 @@ export class ArgSpec { // to use bracket syntax when accessing props on the parsed args object. const rawDest = _.camelCase(dest ?? name); - const destKeypath = - extType && extName ? [extType, extName, rawDest].join('.') : rawDest; + const destKeypath = extType && extName ? [extType, extName, rawDest].join('.') : rawDest; this.defaultValue = defaultValue; this.name = name; @@ -129,7 +128,7 @@ export class ArgSpec { * @param {string} [extName] - Extension name * @returns {string} Schema ID */ - static toSchemaRef (name, extType, extName) { + static toSchemaRef(name, extType, extName) { const baseRef = ArgSpec.toSchemaBaseRef(extType, extName); if (extType && extName) { return [`${baseRef}#`, PROPERTIES, name].join('/'); @@ -142,7 +141,7 @@ export class ArgSpec { * @param {ExtensionType} [extType] - Extension type * @param {string} [extName] - Extension name */ - static toSchemaBaseRef (extType, extName) { + static toSchemaBaseRef(extType, extName) { if (extType && extName) { return `${extType}-${ArgSpec.toNormalizedExtName(extName)}.json`; } @@ -156,7 +155,7 @@ export class ArgSpec { * @param {string} [extName] - Extension name * @returns {string} Unique ID */ - static toArg (name, extType, extName) { + static toArg(name, extType, extName) { const properName = _.kebabCase(name.replace(/^--?/, '')); if (extType && extName) { return [extType, _.kebabCase(extName), properName].join('-'); @@ -169,7 +168,7 @@ export class ArgSpec { * @param {string} extName - Extension name * @returns {string} Normalized extension name */ - static toNormalizedExtName (extName) { + static toNormalizedExtName(extName) { return _.kebabCase(extName); } @@ -178,13 +177,11 @@ export class ArgSpec { * @param {string} schemaId - Root schema ID * @returns { {extType?: ExtensionType, normalizedExtName?: string} } */ - static extensionInfoFromRootSchemaId (schemaId) { + static extensionInfoFromRootSchemaId(schemaId) { const matches = schemaId.match(SCHEMA_ID_REGEXP); if (matches?.groups) { const {extType, normalizedExtName} = - /** @type { {extType: ExtensionType, normalizedExtName: string} } */ ( - matches.groups - ); + /** @type { {extType: ExtensionType, normalizedExtName: string} } */ (matches.groups); return {extType, normalizedExtName}; } return {}; @@ -199,7 +196,7 @@ export class ArgSpec { * @param {ArgSpecOptions} [opts] - Options * @returns {Readonly} */ - static create (name, opts) { + static create(name, opts) { return Object.freeze(new ArgSpec(name, opts)); } @@ -208,7 +205,7 @@ export class ArgSpec { * @returns {string} */ /* istanbul ignore next */ - toString () { + toString() { let str = `[ArgSpec] ${this.name} (${this.ref})`; if (this.extType && this.extName) { str += ` (ext: ${this.extType}/${this.extName})`; diff --git a/packages/appium/lib/schema/cli-args.js b/packages/appium/lib/schema/cli-args.js index b90da2d56..e88304bdd 100644 --- a/packages/appium/lib/schema/cli-args.js +++ b/packages/appium/lib/schema/cli-args.js @@ -1,4 +1,3 @@ - import {ArgumentTypeError} from 'argparse'; import _ from 'lodash'; import {formatErrors as formatErrors} from '../config-file'; @@ -36,7 +35,7 @@ const SHORT_ARG_CUTOFF = 3; * @param {string} [alias] - the alias to convert to a flag * @returns {string} the flag */ -function aliasToFlag (argSpec, alias) { +function aliasToFlag(argSpec, alias) { const {extType, extName, name} = argSpec; const arg = alias ?? name; const isShort = arg.length < SHORT_ARG_CUTOFF; @@ -64,7 +63,7 @@ const screamingSnakeCase = _.flow(_.snakeCase, _.toUpper); * constructor options * @returns */ -function getSchemaValidator ({ref: schemaId}, coerce = _.identity) { +function getSchemaValidator({ref: schemaId}, coerce = _.identity) { /** @param {string} value */ return (value) => { const coerced = coerce(value); @@ -72,9 +71,7 @@ function getSchemaValidator ({ref: schemaId}, coerce = _.identity) { if (_.isEmpty(errors)) { return coerced; } - throw new ArgumentTypeError( - '\n\n' + formatErrors(errors, value, {schemaId}), - ); + throw new ArgumentTypeError('\n\n' + formatErrors(errors, value, {schemaId})); }; } @@ -83,7 +80,7 @@ function getSchemaValidator ({ref: schemaId}, coerce = _.identity) { * @param {AppiumJSONSchema} schema * @returns {string} */ -function makeDescription (schema) { +function makeDescription(schema) { const {appiumCliDescription, description = '', appiumDeprecated} = schema; let desc = appiumCliDescription ?? description; if (appiumDeprecated) { @@ -99,27 +96,20 @@ function makeDescription (schema) { * @param {ArgSpec} argSpec - Argument spec tuple * @returns {[string[], import('argparse').ArgumentOptions]} Tuple of flag and options */ -function subSchemaToArgDef (subSchema, argSpec) { - let { - type, - appiumCliAliases, - appiumCliTransformer, - enum: enumValues, - } = subSchema; +function subSchemaToArgDef(subSchema, argSpec) { + let {type, appiumCliAliases, appiumCliTransformer, enum: enumValues} = subSchema; const {name, arg} = argSpec; const aliases = [ aliasToFlag(argSpec), - .../** @type {string[]} */ (appiumCliAliases ?? []).map((alias) => - aliasToFlag(argSpec, alias), - ), + .../** @type {string[]} */ (appiumCliAliases ?? []).map((alias) => aliasToFlag(argSpec, alias)), ]; /** @type {import('argparse').ArgumentOptions} */ let argOpts = { required: false, - help: makeDescription(subSchema) + help: makeDescription(subSchema), }; /** @@ -182,9 +172,7 @@ function subSchemaToArgDef (subSchema, argSpec) { case TYPENAMES.NULL: // falls through default: { - throw new TypeError( - `Schema property "${arg}": \`${type}\` type unknown or disallowed`, - ); + throw new TypeError(`Schema property "${arg}": \`${type}\` type unknown or disallowed`); } } @@ -198,15 +186,8 @@ function subSchemaToArgDef (subSchema, argSpec) { // by ajv during schema validation in `finalizeSchema()`. the `array` & // `object` types have already added a formatter (see above, so we don't do it // twice). - if ( - type !== TYPENAMES.ARRAY && - type !== TYPENAMES.OBJECT && - appiumCliTransformer - ) { - argTypeFunction = _.flow( - argTypeFunction ?? _.identity, - transformers[appiumCliTransformer], - ); + if (type !== TYPENAMES.ARRAY && type !== TYPENAMES.OBJECT && appiumCliTransformer) { + argTypeFunction = _.flow(argTypeFunction ?? _.identity, transformers[appiumCliTransformer]); } if (argTypeFunction) { @@ -221,7 +202,7 @@ function subSchemaToArgDef (subSchema, argSpec) { argOpts.choices = enumValues.map(String); } else { throw new TypeError( - `Problem with schema for ${arg}; \`enum\` is only supported for \`type: 'string'\``, + `Problem with schema for ${arg}; \`enum\` is only supported for \`type: 'string'\`` ); } } @@ -237,13 +218,9 @@ function subSchemaToArgDef (subSchema, argSpec) { * @returns {import('../cli/args').ArgumentDefinitions} A map of arryas of * aliases to `argparse` arguments; empty if no schema found */ -export function toParserArgs () { +export function toParserArgs() { const flattened = flattenSchema().filter(({schema}) => !schema.appiumCliIgnored); - return new Map( - _.map(flattened, ({schema, argSpec}) => - subSchemaToArgDef(schema, argSpec), - ), - ); + return new Map(_.map(flattened, ({schema, argSpec}) => subSchemaToArgDef(schema, argSpec))); } /** diff --git a/packages/appium/lib/schema/cli-transformers.js b/packages/appium/lib/schema/cli-transformers.js index 6c1f44f15..297b06357 100644 --- a/packages/appium/lib/schema/cli-transformers.js +++ b/packages/appium/lib/schema/cli-transformers.js @@ -1,6 +1,5 @@ - -import { ArgumentTypeError } from 'argparse'; -import { readFileSync } from 'fs'; +import {ArgumentTypeError} from 'argparse'; +import {readFileSync} from 'fs'; import _ from 'lodash'; /** @@ -17,7 +16,7 @@ import _ from 'lodash'; * @param {string} value * @returns {string[]} */ -function parseCsvLine (value) { +function parseCsvLine(value) { return value .split(',') .map((v) => v.trim()) @@ -29,7 +28,7 @@ function parseCsvLine (value) { * @param {string} value * @returns {string[]} */ -function parseCsvFile (value) { +function parseCsvFile(value) { return value .split(/\r?\n/) .map((v) => v.trim()) @@ -67,18 +66,14 @@ export const transformers = { body = readFileSync(value, 'utf8'); } catch (err) { if (err.code !== 'ENOENT') { - throw new ArgumentTypeError( - `Could not read file ${body}: ${err.message}`, - ); + throw new ArgumentTypeError(`Could not read file ${body}: ${err.message}`); } } try { return body ? parseCsvFile(body) : parseCsvLine(value); } catch (err) { - throw new ArgumentTypeError( - 'Must be a comma-delimited string, e.g., "foo,bar,baz"', - ); + throw new ArgumentTypeError('Must be a comma-delimited string, e.g., "foo,bar,baz"'); } }, @@ -107,9 +102,7 @@ export const transformers = { try { const result = JSON.parse(json); if (!_.isPlainObject(result)) { - throw new Error( - `'${_.truncate(result, {length: 100})}' is not an object`, - ); + throw new Error(`'${_.truncate(result, {length: 100})}' is not an object`); } return result; } catch (e) { diff --git a/packages/appium/lib/schema/keywords.js b/packages/appium/lib/schema/keywords.js index b86075f86..60a5cbf20 100644 --- a/packages/appium/lib/schema/keywords.js +++ b/packages/appium/lib/schema/keywords.js @@ -1,5 +1,4 @@ - -import { transformers } from './cli-transformers'; +import {transformers} from './cli-transformers'; /** * Collection of keyword definitions to add to the singleton `Ajv` instance. @@ -27,7 +26,8 @@ export const keywords = { }, minItems: 1, uniqueItems: true, - description: 'List of aliases for the argument. Aliases shorter than three (3) characters will be prefixed with a single dash; otherwise two (2).' + description: + 'List of aliases for the argument. Aliases shorter than three (3) characters will be prefixed with a single dash; otherwise two (2).', }, }, /** @@ -45,7 +45,7 @@ export const keywords = { metaSchema: { type: 'string', minLength: 1, - description: 'Name of the associated property in the parsed CLI arguments object' + description: 'Name of the associated property in the parsed CLI arguments object', }, }, @@ -63,7 +63,7 @@ export const keywords = { metaSchema: { type: 'string', minLength: 1, - description: 'Description to provide in the --help text of the CLI. Overrides `description`' + description: 'Description to provide in the --help text of the CLI. Overrides `description`', }, }, @@ -77,7 +77,8 @@ export const keywords = { metaSchema: { type: 'string', enum: Object.keys(transformers), - description: 'The name of a custom transformer to run against the value as provided via the CLI.' + description: + 'The name of a custom transformer to run against the value as provided via the CLI.', }, }, @@ -89,9 +90,10 @@ export const keywords = { keyword: 'appiumCliIgnored', metaSchema: { type: 'boolean', - description: 'If `true`, Appium will not provide this property as a CLI argument. This is NOT the same as a "hidden" argument.', - enum: [true] - } + description: + 'If `true`, Appium will not provide this property as a CLI argument. This is NOT the same as a "hidden" argument.', + enum: [true], + }, }, /** @@ -104,9 +106,10 @@ export const keywords = { type: 'boolean', description: 'If `true`, this property will be displayed as "deprecated" to the user', enum: [true], - $comment: 'JSON schema draft-2019-09 keyword `deprecated` serves the same purpose. This keyword should itself be deprecated if we move to draft-2019-09!' - } - } + $comment: + 'JSON schema draft-2019-09 keyword `deprecated` serves the same purpose. This keyword should itself be deprecated if we move to draft-2019-09!', + }, + }, }; /** @@ -128,7 +131,6 @@ export const keywords = { * @property {boolean} [appiumDeprecated] */ - /** * @typedef {import('ajv').KeywordDefinition} KeywordDefinition */ diff --git a/packages/appium/lib/schema/schema.js b/packages/appium/lib/schema/schema.js index ec671715e..b18410bb4 100644 --- a/packages/appium/lib/schema/schema.js +++ b/packages/appium/lib/schema/schema.js @@ -1,12 +1,11 @@ - import Ajv from 'ajv'; import addFormats from 'ajv-formats'; import _ from 'lodash'; import path from 'path'; -import { DRIVER_TYPE, PLUGIN_TYPE } from '../constants'; -import { AppiumConfigJsonSchema } from '@appium/schema'; -import { APPIUM_CONFIG_SCHEMA_ID, ArgSpec, SERVER_PROP_NAME } from './arg-spec'; -import { keywords } from './keywords'; +import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants'; +import {AppiumConfigJsonSchema} from '@appium/schema'; +import {APPIUM_CONFIG_SCHEMA_ID, ArgSpec, SERVER_PROP_NAME} from './arg-spec'; +import {keywords} from './keywords'; /** * Key/value pairs go in... but they don't come out. @@ -19,7 +18,7 @@ export class RoachHotelMap extends Map { * @param {K} key * @param {V} value */ - set (key, value) { + set(key, value) { if (this.has(key)) { throw new Error(`${key} is already set`); } @@ -30,11 +29,11 @@ export class RoachHotelMap extends Map { * @param {K} key */ // eslint-disable-next-line no-unused-vars - delete (key) { + delete(key) { return false; } - clear () { + clear() { throw new Error(`Cannot clear RoachHotelMap`); } } @@ -100,7 +99,7 @@ class AppiumSchema { * @see https://npm.im/ajv-formats * @private */ - constructor () { + constructor() { this._ajv = AppiumSchema._instantiateAjv(); } @@ -111,7 +110,7 @@ class AppiumSchema { * Binds public methods to the instance. * @returns {AppiumSchema} */ - static create () { + static create() { if (!AppiumSchema._instance) { const instance = new AppiumSchema(); AppiumSchema._instance = instance; @@ -143,7 +142,7 @@ class AppiumSchema { * @param {string} extName - Name * @returns {boolean} If registered */ - hasRegisteredSchema (extType, extName) { + hasRegisteredSchema(extType, extName) { return this._registeredSchemas[extType].has(extName); } @@ -152,11 +151,11 @@ class AppiumSchema { * successfully and {@link AppiumSchema.reset reset} has not been called since. * @returns {boolean} If finalized */ - isFinalized () { + isFinalized() { return Boolean(this._finalizedSchemas); } - getAllArgSpecs () { + getAllArgSpecs() { return this._argSpecs; } @@ -179,11 +178,9 @@ class AppiumSchema { * @throws {Error} If the schema is not valid * @returns {Readonly>} Record of schema IDs to full schema objects */ - finalize () { + finalize() { if (this.isFinalized()) { - return /** @type {NonNullable} */ ( - this._finalizedSchemas - ); + return /** @type {NonNullable} */ (this._finalizedSchemas); } const ajv = this._ajv; @@ -210,12 +207,7 @@ class AppiumSchema { } }; - addArgSpecs( - _.omit(baseSchema.properties.server.properties, [ - DRIVER_TYPE, - PLUGIN_TYPE, - ]), - ); + addArgSpecs(_.omit(baseSchema.properties.server.properties, [DRIVER_TYPE, PLUGIN_TYPE])); /** * @type {Record} @@ -234,8 +226,10 @@ class AppiumSchema { const $ref = ArgSpec.toSchemaBaseRef(extType, extName); schema.$id = $ref; schema.additionalProperties = false; // this makes `schema` become a `StrictSchemaObject` - baseSchema.properties.server.properties[extType].properties[extName] = - {$ref, $comment: extName}; + baseSchema.properties.server.properties[extType].properties[extName] = { + $ref, + $comment: extName, + }; ajv.validateSchema(schema, true); addArgSpecs(schema.properties, extType, extName); ajv.addSchema(schema, $ref); @@ -243,7 +237,7 @@ class AppiumSchema { }); return baseSchema; }, - baseSchema, + baseSchema ); ajv.addSchema(finalSchema, APPIUM_CONFIG_SCHEMA_ID); @@ -259,12 +253,12 @@ class AppiumSchema { * @private * @returns {Ajv} */ - static _instantiateAjv () { + static _instantiateAjv() { const ajv = addFormats( new Ajv({ // without this not much validation actually happens allErrors: true, - }), + }) ); // add custom keywords to ajv. see schema-keywords.js @@ -286,7 +280,7 @@ class AppiumSchema { * If you need to call {@link AppiumSchema.finalize} again, you'll want to call this first. * @returns {void} */ - reset () { + reset() { for (const schemaId of Object.keys(this._finalizedSchemas ?? {})) { this._ajv.removeSchema(schemaId); } @@ -313,11 +307,9 @@ class AppiumSchema { * @throws {SchemaNameConflictError} If the schema is an invalid * @returns {void} */ - registerSchema (extType, extName, schema) { + registerSchema(extType, extName, schema) { if (!(extType && extName) || _.isUndefined(schema)) { - throw new TypeError( - 'Expected extension type, extension name, and a defined schema', - ); + throw new TypeError('Expected extension type, extension name, and a defined schema'); } if (!AppiumSchema.isSupportedSchemaType(schema)) { throw new SchemaUnsupportedSchemaError(schema, extType, extName); @@ -341,7 +333,7 @@ class AppiumSchema { * @param {string} [extName] - Extension name * @returns {ArgSpec|undefined} ArgSpec or `undefined` if not found */ - getArgSpec (name, extType, extName) { + getArgSpec(name, extType, extName) { return this._argSpecs.get(ArgSpec.toArg(name, extType, extName)); } @@ -352,7 +344,7 @@ class AppiumSchema { * @param {string} [extName] - Extension name * @returns {boolean} `true` if such an {@link ArgSpec} exists */ - hasArgSpec (name, extType, extName) { + hasArgSpec(name, extType, extName) { return this._argSpecs.has(ArgSpec.toArg(name, extType, extName)); } @@ -368,7 +360,7 @@ class AppiumSchema { * properties. Base arguments (server arguments) are always at the top level. * @returns {DefaultValues} */ - getDefaults (flatten = /** @type {Flattened} */ (true)) { + getDefaults(flatten = /** @type {Flattened} */ (true)) { if (!this.isFinalized()) { throw new SchemaFinalizationError(); } @@ -383,17 +375,17 @@ class AppiumSchema { /** @type {DefaultReducer} */ const reducer = flatten ? (defaults, {defaultValue, dest}) => { - if (!_.isUndefined(defaultValue)) { - defaults[dest] = defaultValue; + if (!_.isUndefined(defaultValue)) { + defaults[dest] = defaultValue; + } + return defaults; } - return defaults; - } : (defaults, {defaultValue, dest}) => { - if (!_.isUndefined(defaultValue)) { - _.set(defaults, dest, defaultValue); - } - return defaults; - }; + if (!_.isUndefined(defaultValue)) { + _.set(defaults, dest, defaultValue); + } + return defaults; + }; /** @type {DefaultValues} */ const retval = {}; @@ -407,12 +399,12 @@ class AppiumSchema { * @param {string} extName - Extension name * @returns {Record} */ - getDefaultsForExtension (extType, extName) { + getDefaultsForExtension(extType, extName) { if (!this.isFinalized()) { throw new SchemaFinalizationError(); } const specs = [...this._argSpecs.values()].filter( - (spec) => spec.extType === extType && spec.extName === extName, + (spec) => spec.extType === extType && spec.extName === extName ); return specs.reduce((defaults, {defaultValue, rawDest}) => { if (!_.isUndefined(defaultValue)) { @@ -436,7 +428,7 @@ class AppiumSchema { * @throws If {@link AppiumSchema.finalize} has not been called yet. * @returns {FlattenedSchema} */ - flatten () { + flatten() { const schema = this.getSchema(); /** @type { {properties: SchemaObject, prefix: string[]}[] } */ @@ -463,12 +455,11 @@ class AppiumSchema { // this can happen if an extension schema supplies a $ref to a non-existent schema throw new SchemaUnknownSchemaError($ref); } - const {normalizedExtName} = - ArgSpec.extensionInfoFromRootSchemaId($ref); + const {normalizedExtName} = ArgSpec.extensionInfoFromRootSchemaId($ref); if (!normalizedExtName) { /* istanbul ignore next */ throw new ReferenceError( - `Could not determine extension name from schema ID ${$ref}. This is a bug.`, + `Could not determine extension name from schema ID ${$ref}. This is a bug.` ); } stack.push({ @@ -477,15 +468,11 @@ class AppiumSchema { }); } else if (key !== DRIVER_TYPE && key !== PLUGIN_TYPE) { const [extType, extName] = prefix; - const argSpec = this.getArgSpec( - key, - /** @type {ExtensionType} */ (extType), - extName, - ); + const argSpec = this.getArgSpec(key, /** @type {ExtensionType} */ (extType), extName); if (!argSpec) { /* istanbul ignore next */ throw new ReferenceError( - `Unknown argument with key ${key}, extType ${extType} and extName ${extName}. This is a bug.`, + `Unknown argument with key ${key}, extType ${extType} and extName ${extName}. This is a bug.` ); } flattened.push({schema: _.cloneDeep(value), argSpec}); @@ -503,7 +490,7 @@ class AppiumSchema { * @throws If the schema has not yet been finalized * @returns {SchemaObject} */ - getSchema (ref = APPIUM_CONFIG_SCHEMA_ID) { + getSchema(ref = APPIUM_CONFIG_SCHEMA_ID) { return /** @type {SchemaObject} */ (this._getValidator(ref).schema); } @@ -513,7 +500,7 @@ class AppiumSchema { * @private * @returns {import('ajv').ValidateFunction} */ - _getValidator (id = APPIUM_CONFIG_SCHEMA_ID) { + _getValidator(id = APPIUM_CONFIG_SCHEMA_ID) { const validator = this._ajv.getSchema(id); if (!validator) { if (id === APPIUM_CONFIG_SCHEMA_ID) { @@ -533,11 +520,9 @@ class AppiumSchema { * @public * @returns {import('ajv').ErrorObject[]} Array of errors, if any. */ - validate (value, ref = APPIUM_CONFIG_SCHEMA_ID) { + validate(value, ref = APPIUM_CONFIG_SCHEMA_ID) { const validator = this._getValidator(ref); - return !validator(value) && _.isArray(validator.errors) - ? [...validator.errors] - : []; + return !validator(value) && _.isArray(validator.errors) ? [...validator.errors] : []; } /** @@ -545,7 +530,7 @@ class AppiumSchema { * @param {string} filename * @returns {boolean} */ - static isAllowedSchemaFileExtension (filename) { + static isAllowedSchemaFileExtension(filename) { return ALLOWED_SCHEMA_EXTENSIONS.has(path.extname(filename)); } @@ -554,7 +539,7 @@ class AppiumSchema { * @param {any} schema - Schema to check * @returns {schema is SchemaObject} */ - static isSupportedSchemaType (schema) { + static isSupportedSchemaType(schema) { return _.isPlainObject(schema) && schema.$async !== true; } } @@ -569,7 +554,7 @@ export class SchemaFinalizationError extends Error { */ code = 'APPIUMERR_SCHEMA_FINALIZATION'; - constructor () { + constructor() { super('Schema not yet finalized; `finalize()` must be called first.'); } } @@ -594,10 +579,8 @@ export class SchemaNameConflictError extends Error { * @param {ExtensionType} extType * @param {string} extName */ - constructor (extType, extName) { - super( - `Name for ${extType} schema "${extName}" conflicts with an existing schema`, - ); + constructor(extType, extName) { + super(`Name for ${extType} schema "${extName}" conflicts with an existing schema`); this.data = {extType, extName}; } } @@ -619,7 +602,7 @@ export class SchemaUnknownSchemaError extends ReferenceError { /** * @param {string} schemaId */ - constructor (schemaId) { + constructor(schemaId) { super(`Unknown schema: "${schemaId}"`); this.data = {schemaId}; } @@ -647,7 +630,7 @@ export class SchemaUnsupportedSchemaError extends TypeError { * @param {ExtensionType} extType * @param {string} extName */ - constructor (schema, extType, extName) { + constructor(schema, extType, extName) { // https://github.com/Microsoft/TypeScript/issues/8277 super( (() => { @@ -662,12 +645,12 @@ export class SchemaUnsupportedSchemaError extends TypeError { /* istanbul ignore next */ throw new TypeError( `schema IS supported; this error should not be thrown (this is a bug). value of schema: ${JSON.stringify( - schema, - )}`, + schema + )}` ); } return `${msg} schema must be a plain object without a true "$async" property`; - })(), + })() ); this.data = {schema, extType, extName}; } diff --git a/packages/appium/lib/utils.js b/packages/appium/lib/utils.js index 667bcd87b..a926a7fc2 100644 --- a/packages/appium/lib/utils.js +++ b/packages/appium/lib/utils.js @@ -1,8 +1,7 @@ - import _ from 'lodash'; import logger from './logger'; -import { processCapabilities, PROTOCOLS } from '@appium/base-driver'; -import { inspect as dump } from 'util'; +import {processCapabilities, PROTOCOLS} from '@appium/base-driver'; +import {inspect as dump} from 'util'; const W3C_APPIUM_PREFIX = 'appium'; @@ -22,12 +21,13 @@ const isStdoutTTY = process.stdout.isTTY; */ const inspect = _.flow( _.partialRight( - /** @type {(object: any, options: import('util').InspectOptions) => string} */(dump), + /** @type {(object: any, options: import('util').InspectOptions) => string} */ (dump), {colors: true, depth: null, compact: !isStdoutTTY} ), (...args) => { logger.info(...args); - }); + } +); /** * Takes the caps that were provided in the request and translates them @@ -39,19 +39,25 @@ const inspect = _.flow( * @param {import('@appium/types').DefaultCapabilitiesConfig} [defaultCapabilities] * @returns {ParsedDriverCaps|InvalidCaps} */ -function parseCapsForInnerDriver (jsonwpCapabilities, w3cCapabilities, constraints = {}, defaultCapabilities = {}) { +function parseCapsForInnerDriver( + jsonwpCapabilities, + w3cCapabilities, + constraints = {}, + defaultCapabilities = {} +) { // Check if the caller sent JSONWP caps, W3C caps, or both - const hasW3CCaps = _.isPlainObject(w3cCapabilities) && + const hasW3CCaps = + _.isPlainObject(w3cCapabilities) && (_.has(w3cCapabilities, 'alwaysMatch') || _.has(w3cCapabilities, 'firstMatch')); const hasJSONWPCaps = _.isPlainObject(jsonwpCapabilities); - let desiredCaps = /** @type {ParsedDriverCaps['desiredCaps']} */({}); + let desiredCaps = /** @type {ParsedDriverCaps['desiredCaps']} */ ({}); /** @type {ParsedDriverCaps['processedW3CCapabilities']} */ let processedW3CCapabilities; /** @type {ParsedDriverCaps['processedJsonwpCapabilities']} */ let processedJsonwpCapabilities; if (!hasW3CCaps) { - return /** @type {InvalidCaps} */({ + return /** @type {InvalidCaps} */ ({ protocol: PROTOCOLS.W3C, error: new Error('W3C capabilities should be provided'), }); @@ -70,16 +76,23 @@ function parseCapsForInnerDriver (jsonwpCapabilities, w3cCapabilities, constrain for (const [defaultCapKey, defaultCapValue] of _.toPairs(defaultCapabilities)) { let isCapAlreadySet = false; // Check if the key is already present in firstMatch entries - for (const firstMatchEntry of (w3cCapabilities.firstMatch || [])) { - if (_.isPlainObject(firstMatchEntry) - && _.has(removeAppiumPrefixes(firstMatchEntry), removeAppiumPrefix(defaultCapKey))) { + for (const firstMatchEntry of w3cCapabilities.firstMatch || []) { + if ( + _.isPlainObject(firstMatchEntry) && + _.has(removeAppiumPrefixes(firstMatchEntry), removeAppiumPrefix(defaultCapKey)) + ) { isCapAlreadySet = true; break; } } // Check if the key is already present in alwaysMatch entries - isCapAlreadySet = isCapAlreadySet || (_.isPlainObject(w3cCapabilities.alwaysMatch) - && _.has(removeAppiumPrefixes(w3cCapabilities.alwaysMatch), removeAppiumPrefix(defaultCapKey))); + isCapAlreadySet = + isCapAlreadySet || + (_.isPlainObject(w3cCapabilities.alwaysMatch) && + _.has( + removeAppiumPrefixes(w3cCapabilities.alwaysMatch), + removeAppiumPrefix(defaultCapKey) + )); if (isCapAlreadySet) { // Skip if the key is already present in the provided caps continue; @@ -94,7 +107,10 @@ function parseCapsForInnerDriver (jsonwpCapabilities, w3cCapabilities, constrain } } if (hasJSONWPCaps) { - jsonwpCapabilities = {...removeAppiumPrefixes(defaultCapabilities), ...jsonwpCapabilities}; + jsonwpCapabilities = { + ...removeAppiumPrefixes(defaultCapabilities), + ...jsonwpCapabilities, + }; } } @@ -111,7 +127,7 @@ function parseCapsForInnerDriver (jsonwpCapabilities, w3cCapabilities, constrain desiredCaps = processCapabilities(w3cCapabilities, constraints, true); } catch (error) { logger.info(`Could not parse W3C capabilities: ${error.message}`); - return /** @type {InvalidCaps} */({ + return /** @type {InvalidCaps} */ ({ desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, @@ -127,7 +143,12 @@ function parseCapsForInnerDriver (jsonwpCapabilities, w3cCapabilities, constrain }; } - return /** @type {ParsedDriverCaps} */({desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol}); + return /** @type {ParsedDriverCaps} */ ({ + desiredCaps, + processedJsonwpCapabilities, + processedW3CCapabilities, + protocol, + }); } /** @@ -135,7 +156,7 @@ function parseCapsForInnerDriver (jsonwpCapabilities, w3cCapabilities, constrain * @param {Capabilities} caps Desired capabilities object * @returns {AppiumW3CCapabilities} */ -function insertAppiumPrefixes (caps) { +function insertAppiumPrefixes(caps) { // Standard, non-prefixed capabilities (see https://www.w3.org/TR/webdriver/#dfn-table-of-standard-capabilities) const STANDARD_CAPS = [ 'browserName', @@ -146,7 +167,7 @@ function insertAppiumPrefixes (caps) { 'proxy', 'setWindowRect', 'timeouts', - 'unhandledPromptBehavior' + 'unhandledPromptBehavior', ]; let prefixedCaps = {}; @@ -165,7 +186,7 @@ function insertAppiumPrefixes (caps) { * @param {AppiumW3CCapabilities} caps * @returns {Capabilities} */ -function removeAppiumPrefixes (caps) { +function removeAppiumPrefixes(caps) { if (!_.isPlainObject(caps)) { return caps; } @@ -178,12 +199,12 @@ function removeAppiumPrefixes (caps) { return fixedCaps; } -function removeAppiumPrefix (key) { +function removeAppiumPrefix(key) { const prefix = `${W3C_APPIUM_PREFIX}:`; return _.startsWith(key, prefix) ? key.substring(prefix.length) : key; } -function getPackageVersion (pkgName) { +function getPackageVersion(pkgName) { const pkgInfo = require(`${pkgName}/package.json`) || {}; return pkgInfo.version; } @@ -204,7 +225,7 @@ function getPackageVersion (pkgName) { * setting items or a dictionary containing parsed Appium setting names along with * their values. */ -function pullSettings (caps) { +function pullSettings(caps) { if (!_.isPlainObject(caps) || _.isEmpty(caps)) { return {}; } @@ -223,8 +244,12 @@ function pullSettings (caps) { } export { - inspect, parseCapsForInnerDriver, insertAppiumPrefixes, - getPackageVersion, pullSettings, removeAppiumPrefixes + inspect, + parseCapsForInnerDriver, + insertAppiumPrefixes, + getPackageVersion, + pullSettings, + removeAppiumPrefixes, }; /** diff --git a/packages/appium/sample-code/quickstarts/js/test.js b/packages/appium/sample-code/quickstarts/js/test.js index b2789aa12..9de74772e 100644 --- a/packages/appium/sample-code/quickstarts/js/test.js +++ b/packages/appium/sample-code/quickstarts/js/test.js @@ -1,29 +1,29 @@ -const { remote } = require('webdriverio') +const {remote} = require('webdriverio'); const capabilities = { - 'platformName': 'Android', + platformName: 'Android', 'appium:automationName': 'UiAutomator2', 'appium:deviceName': 'Android', 'appium:appPackage': 'com.android.settings', 'appium:appActivity': '.Settings', -} +}; const wdOpts = { host: process.env.APPIUM_HOST || 'localhost', port: parseInt(process.env.APPIUM_PORT, 10) || 4723, logLevel: 'info', capabilities, -} +}; -async function runTest () { - const driver = await remote(wdOpts) +async function runTest() { + const driver = await remote(wdOpts); try { - const batteryItem = await driver.$('//*[@text="Battery"]') - await batteryItem.click() + const batteryItem = await driver.$('//*[@text="Battery"]'); + await batteryItem.click(); } finally { - await driver.pause(1000) - await driver.deleteSession() + await driver.pause(1000); + await driver.deleteSession(); } } -runTest().catch(console.error) +runTest().catch(console.error); diff --git a/packages/appium/scripts/check-npm-pack-files.js b/packages/appium/scripts/check-npm-pack-files.js index 384bc5877..097e18738 100755 --- a/packages/appium/scripts/check-npm-pack-files.js +++ b/packages/appium/scripts/check-npm-pack-files.js @@ -4,7 +4,12 @@ const path = require('path'); const childProcess = require('child_process'); const _ = require('lodash'); -const res = JSON.parse(childProcess.execSync('npm pack --dry-run --json --ignore-scripts', {cwd: path.join(__dirname, '..'), encoding: 'utf8'}))[0]; +const res = JSON.parse( + childProcess.execSync('npm pack --dry-run --json --ignore-scripts', { + cwd: path.join(__dirname, '..'), + encoding: 'utf8', + }) +)[0]; // List of files we are testing to make sure they are included in package const testFiles = [ @@ -16,8 +21,10 @@ const testFiles = [ const missingFiles = _.without(testFiles, ..._.map(res.files, 'path')); if (!_.isEmpty(missingFiles)) { - throw new Error(`Files [${missingFiles.join(', ')}] are not included in package.json "files". ` + - `Please make sure these files are included before publishing.`); + throw new Error( + `Files [${missingFiles.join(', ')}] are not included in package.json "files". ` + + `Please make sure these files are included before publishing.` + ); } process.exit(0); diff --git a/packages/appium/scripts/parse-yml-commands.js b/packages/appium/scripts/parse-yml-commands.js index 75c7003aa..05bbe766f 100755 --- a/packages/appium/scripts/parse-yml-commands.js +++ b/packages/appium/scripts/parse-yml-commands.js @@ -4,21 +4,21 @@ const path = require('path'); const yaml = require('yaml'); -const { fs, util } = require('@appium/support'); +const {fs, util} = require('@appium/support'); const validate = require('validate.js'); const Handlebars = require('handlebars'); const _ = require('lodash'); -const { asyncify } = require('asyncbox'); +const {asyncify} = require('asyncbox'); const url = require('url'); const log = require('fancy-log'); -validate.validators.array = function array (value, options, key, attributes) { +validate.validators.array = function array(value, options, key, attributes) { if (attributes[key] && !validate.isArray(attributes[key])) { return `must be an array`; } }; -validate.validators.hasAttributes = function hasAttributes (value, options) { +validate.validators.hasAttributes = function hasAttributes(value, options) { if (!value) { return; } @@ -36,7 +36,7 @@ validate.validators.hasAttributes = function hasAttributes (value, options) { } }; -validate.validators.hasPossibleAttributes = function hasPossibleAttributes (value, options) { +validate.validators.hasPossibleAttributes = function hasPossibleAttributes(value, options) { if (!value) { return; } @@ -62,30 +62,40 @@ const CLIENT_URL_TYPES = { }; const validator = { - 'name': {presence: true}, - 'short_description': {presence: true}, - 'example_usage': {}, + name: {presence: true}, + short_description: {presence: true}, + example_usage: {}, 'example_usage.java': {}, 'example_usage.javascript_wdio': {}, 'example_usage.javascript_wd': {}, 'example_usage.ruby': {}, 'example_usage.ruby_core': {}, 'example_usage.csharp': {}, - 'description': {}, + description: {}, 'client_docs.java': {hasPossibleAttributes: _.keys(CLIENT_URL_TYPES)}, - 'client_docs.javascript_wdio': {hasPossibleAttributes: _.keys(CLIENT_URL_TYPES)}, - 'client_docs.javascript_wd': {hasPossibleAttributes: _.keys(CLIENT_URL_TYPES)}, + 'client_docs.javascript_wdio': { + hasPossibleAttributes: _.keys(CLIENT_URL_TYPES), + }, + 'client_docs.javascript_wd': { + hasPossibleAttributes: _.keys(CLIENT_URL_TYPES), + }, 'client_docs.ruby': {hasPossibleAttributes: _.keys(CLIENT_URL_TYPES)}, 'client_docs.ruby_core': {hasPossibleAttributes: _.keys(CLIENT_URL_TYPES)}, 'client_docs.csharp': {hasPossibleAttributes: _.keys(CLIENT_URL_TYPES)}, - 'endpoint': {presence: true}, - 'driver_support': {presence: true}, + endpoint: {presence: true}, + driver_support: {presence: true}, 'endpoint.url': {presence: true}, - 'endpoint.url_parameters': {array: true, hasAttributes: ['name', 'description']}, - 'endpoint.json_parameters': {array: true, hasAttributes: ['name', 'description']}, - 'endpoint.response': {hasAttributes: ['type', 'description'] }, - 'specifications': {presence: true}, - 'links': {array: true, hasAttributes: ['name', 'url']}, + 'endpoint.url_parameters': { + array: true, + hasAttributes: ['name', 'description'], + }, + 'endpoint.json_parameters': { + array: true, + hasAttributes: ['name', 'description'], + }, + 'endpoint.response': {hasAttributes: ['type', 'description']}, + specifications: {presence: true}, + links: {array: true, hasAttributes: ['name', 'url']}, }; // What range of platforms do the driver's support @@ -111,7 +121,7 @@ const appiumRanges = { const rootFolder = path.join(__dirname, '..', '..', '..'); // Create Handlebars helper that shows a version range -Handlebars.registerHelper('versions', function versionHelper (object, name, driverName) { +Handlebars.registerHelper('versions', function versionHelper(object, name, driverName) { if (!object) { return 'None'; } @@ -153,7 +163,7 @@ Handlebars.registerHelper('versions', function versionHelper (object, name, driv Handlebars.registerHelper('hyphenate', (str) => str.replace('_', '-')); Handlebars.registerHelper('uppercase', (str) => str.toUpperCase()); -Handlebars.registerHelper('capitalize', function capitalizeDriver (driverName) { +Handlebars.registerHelper('capitalize', function capitalizeDriver(driverName) { switch (driverName.toLowerCase()) { case 'xcuitest': return 'XCUITest'; @@ -166,11 +176,13 @@ Handlebars.registerHelper('capitalize', function capitalizeDriver (driverName) { case 'espresso': return 'Espresso'; default: - return driverName.length === 0 ? driverName : driverName[0].toUpperCase() + driverName.substr(1); + return driverName.length === 0 + ? driverName + : driverName[0].toUpperCase() + driverName.substr(1); } }); -Handlebars.registerHelper('if_eq', function ifEq (a, b, opts) { +Handlebars.registerHelper('if_eq', function ifEq(a, b, opts) { if (a === b) { return opts.fn(this); } else { @@ -178,21 +190,21 @@ Handlebars.registerHelper('if_eq', function ifEq (a, b, opts) { } }); -function getBaseHostname (fullUrl) { +function getBaseHostname(fullUrl) { const baseUrl = url.parse(fullUrl); return baseUrl.hostname; } -Handlebars.registerHelper('base_url', function baseUrl (fullUrl) { +Handlebars.registerHelper('base_url', function baseUrl(fullUrl) { return getBaseHostname(fullUrl); }); -Handlebars.registerHelper('client_url', function clientUrl (clientUrl) { +Handlebars.registerHelper('client_url', function clientUrl(clientUrl) { if (!clientUrl) { return; } - const createUrlString = function createUrlString (clientUrl, name = getBaseHostname(clientUrl)) { + const createUrlString = function createUrlString(clientUrl, name = getBaseHostname(clientUrl)) { return `[${name}](${clientUrl})`; }; @@ -204,20 +216,24 @@ Handlebars.registerHelper('client_url', function clientUrl (clientUrl) { for (const item of clientUrl) { for (let [key, value] of _.toPairs(item)) { key = key.toLowerCase(); - const urlStr = CLIENT_URL_TYPES[key] === 'hostname' - ? createUrlString(value) - : createUrlString(value, CLIENT_URL_TYPES[key]); + const urlStr = + CLIENT_URL_TYPES[key] === 'hostname' + ? createUrlString(value) + : createUrlString(value, CLIENT_URL_TYPES[key]); urlStrings.push(urlStr); } } return urlStrings.join(' '); }); -async function registerSpecUrlHelper () { - const routesFile = await fs.readFile(require.resolve('@appium/base-driver/lib/protocol/routes.js'), 'utf8'); +async function registerSpecUrlHelper() { + const routesFile = await fs.readFile( + require.resolve('@appium/base-driver/lib/protocol/routes.js'), + 'utf8' + ); const routesFileLines = routesFile.split('\n'); - Handlebars.registerHelper('spec_url', function specUrl (specUrl, endpoint) { + Handlebars.registerHelper('spec_url', function specUrl(specUrl, endpoint) { // return the url if it is not a link to our routes doc if (!specUrl.includes('routes.js')) { return specUrl; @@ -245,7 +261,9 @@ async function registerSpecUrlHelper () { } } if (_.isUndefined(index)) { - throw new Error(`Unable to find entry in 'appium-base-driver#routes' for endpoint '${endpoint}'`); + throw new Error( + `Unable to find entry in 'appium-base-driver#routes' for endpoint '${endpoint}'` + ); } return `${specUrl}#L${index}`; @@ -254,7 +272,7 @@ async function registerSpecUrlHelper () { const YAML_DIR = path.join(__dirname, '..', 'commands-yml'); -async function generateCommands () { +async function generateCommands() { await registerSpecUrlHelper(); const commands = path.resolve(YAML_DIR, 'commands/**/*.yml'); @@ -262,7 +280,10 @@ async function generateCommands () { await fs.rimraf(path.resolve(rootFolder, 'docs', 'en', 'commands')); // get the template from which the md files will be created - const template = Handlebars.compile(await fs.readFile(path.resolve(YAML_DIR, 'template.md'), 'utf8'), {noEscape: true, strict: true}); + const template = Handlebars.compile( + await fs.readFile(path.resolve(YAML_DIR, 'template.md'), 'utf8'), + {noEscape: true, strict: true} + ); let fileCount = 0; for (const filename of await fs.glob(commands)) { @@ -283,7 +304,10 @@ async function generateCommands () { // Write the markdown to its right place const ext = path.extname(relativeFilename); - const markdownPath = `${relativeFilename.substring(0, relativeFilename.length - ext.length)}.md`; + const markdownPath = `${relativeFilename.substring( + 0, + relativeFilename.length - ext.length + )}.md`; const outfile = path.resolve(rootFolder, 'docs', 'en', markdownPath); log(` Writing to: ${outfile}`); await fs.mkdirp(path.dirname(outfile)); @@ -294,8 +318,8 @@ async function generateCommands () { log(`Done writing ${fileCount} command documents`); } -async function generateCommandIndex () { - function getTree (element, path) { +async function generateCommandIndex() { + function getTree(element, path) { let node = { name: element[0], }; @@ -321,9 +345,12 @@ async function generateCommandIndex () { commands.push(getTree(el, '/docs/en/commands')); } - const commandTemplate = Handlebars.compile(await fs.readFile(path.resolve(YAML_DIR, 'api-template.md'), 'utf8'), {noEscape: true, strict: true}); + const commandTemplate = Handlebars.compile( + await fs.readFile(path.resolve(YAML_DIR, 'api-template.md'), 'utf8'), + {noEscape: true, strict: true} + ); - async function writeIndex (index, commands, indexPath) { + async function writeIndex(index, commands, indexPath) { log(`Creating API index '${index}'`); const commandMarkdown = commandTemplate({ commands, @@ -336,7 +363,7 @@ async function generateCommandIndex () { await writeIndex(apiIndex, commands); log(`Done writing main API index`); - async function writeIndividualIndexes (command) { + async function writeIndividualIndexes(command) { if (!util.hasValue(command.commands)) { // this is a leaf, so end return; @@ -361,7 +388,7 @@ async function generateCommandIndex () { } } -async function main () { +async function main() { await generateCommands(); await generateCommandIndex(); } diff --git a/packages/appium/scripts/postinstall.js b/packages/appium/scripts/postinstall.js index bb5801d3b..14cb966da 100755 --- a/packages/appium/scripts/postinstall.js +++ b/packages/appium/scripts/postinstall.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /* eslint-disable no-console, promise/prefer-await-to-then */ -async function main () { +async function main() { const driverEnv = process.env.npm_config_drivers; const pluginEnv = process.env.npm_config_plugins; @@ -14,20 +14,29 @@ async function main () { try { extension = require('../build/lib/cli/extension'); } catch (e) { - throw new Error(`Could not load extension CLI file; has the project been transpiled? ` + - `(${e.message})`); + throw new Error( + `Could not load extension CLI file; has the project been transpiled? ` + `(${e.message})` + ); } const {DEFAULT_APPIUM_HOME, DRIVER_TYPE, PLUGIN_TYPE} = require('./build/lib/extension-config'); const {runExtensionCommand} = extension; const appiumHome = process.env.npm_config_appium_home || DEFAULT_APPIUM_HOME; - const specs = [[DRIVER_TYPE, driverEnv], [PLUGIN_TYPE, pluginEnv]]; + const specs = [ + [DRIVER_TYPE, driverEnv], + [PLUGIN_TYPE, pluginEnv], + ]; for (const [type, extEnv] of specs) { if (extEnv) { for (const ext of extEnv.split(',')) { try { - await checkAndInstallExtension({runExtensionCommand, appiumHome, type, ext}); + await checkAndInstallExtension({ + runExtensionCommand, + appiumHome, + type, + ext, + }); } catch (e) { console.log(`There was an error checking and installing ${type} ${ext}: ${e.message}`); } @@ -36,36 +45,39 @@ async function main () { } } -async function checkAndInstallExtension ({ - runExtensionCommand, - appiumHome, - type, - ext, -}) { - const extList = await runExtensionCommand({ - appiumHome, - [`${type}Command`]: 'list', - showInstalled: true, - suppressOutput: true, - }, type); +async function checkAndInstallExtension({runExtensionCommand, appiumHome, type, ext}) { + const extList = await runExtensionCommand( + { + appiumHome, + [`${type}Command`]: 'list', + showInstalled: true, + suppressOutput: true, + }, + type + ); if (extList[ext]) { console.log(`The ${type} ${ext} was already installed, skipping...`); return; } console.log(`Installing the ${type} ${ext}...`); - await runExtensionCommand({ - appiumHome, - [`${type}Command`]: 'install', - [type]: ext, - suppressOutput: true, - }, type); + await runExtensionCommand( + { + appiumHome, + [`${type}Command`]: 'install', + [type]: ext, + suppressOutput: true, + }, + type + ); } if (require.main === module) { - main().then(() => { - process.exit(0); - }).catch((e) => { - console.error(e); - process.exit(1); - }); + main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); } diff --git a/packages/appium/scripts/write-server-args-docs.js b/packages/appium/scripts/write-server-args-docs.js index c81658d2d..0ed168aa2 100755 --- a/packages/appium/scripts/write-server-args-docs.js +++ b/packages/appium/scripts/write-server-args-docs.js @@ -16,16 +16,7 @@ try { /** @type {import('../lib/cli/args').ArgumentDefinitions} */ const appiumArguments = parser.getParser().rawArgs; const docFile = path.normalize( - path.join( - __dirname, - '..', - '..', - '..', - 'docs', - 'en', - 'writing-running-appium', - 'server-args.md', - ), + path.join(__dirname, '..', '..', '..', 'docs', 'en', 'writing-running-appium', 'server-args.md') ); let md = ` @@ -44,7 +35,7 @@ All flags are optional, but some are required in conjunction with certain others |Flag|Default|Description|Example| |----|-------|-----------|-------| `; -appiumArguments.forEach(function handleArguments (argOpts, argNames) { +appiumArguments.forEach(function handleArguments(argOpts, argNames) { // handle empty objects if (JSON.stringify(argOpts.default) === '{}') { argOpts.default = '{}'; @@ -62,7 +53,7 @@ try { console.error( 'New docs written! Do not forget to commit:\ngit add -A %s && git commit -m "Update %s"', path.relative(process.cwd(), docFile), - path.basename(docFile), + path.basename(docFile) ); } catch (err) { console.error('Could not write to file %s: %s', docFile, err); diff --git a/packages/appium/test/e2e/cli.e2e.spec.js b/packages/appium/test/e2e/cli.e2e.spec.js index 16be552d3..a8c37c17d 100644 --- a/packages/appium/test/e2e/cli.e2e.spec.js +++ b/packages/appium/test/e2e/cli.e2e.spec.js @@ -34,9 +34,7 @@ describe('CLI behavior', function () { */ let appiumHome; - const testDriverPath = path.dirname( - resolveFixture('test-driver/package.json') - ); + const testDriverPath = path.dirname(resolveFixture('test-driver/package.json')); beforeEach(function () { this.timeout(30000); @@ -80,10 +78,7 @@ describe('CLI behavior', function () { appiumHomePkgPath = path.join(appiumHome, 'package.json'); runJson = runAppiumJson(appiumHome); // an example package.json referencing appium dependency - await fs.copyFile( - resolveFixture('cli/appium-dependency.package.json'), - appiumHomePkgPath - ); + await fs.copyFile(resolveFixture('cli/appium-dependency.package.json'), appiumHomePkgPath); }); after(async function () { @@ -92,9 +87,7 @@ describe('CLI behavior', function () { describe('without drivers installed', function () { it('should list no drivers', async function () { - const res = /** @type {ExtensionListData} */ ( - await runJson([DRIVER_TYPE, LIST]) - ); + const res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST])); res.should.satisfy( /** @param {typeof res} value */ (value) => Object.values(value).every(({installed}) => !installed) @@ -108,18 +101,12 @@ describe('CLI behavior', function () { }); it('should list the driver', async function () { - const res = /** @type {ExtensionListData} */ ( - await runJson([DRIVER_TYPE, LIST]) - ); + const res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST])); res.should.have.property('fake'); }); it('should be resolvable from the local directory', function () { - (() => - resolveFrom( - appiumHome, - '@appium/fake-driver/package.json' - )).should.not.throw(); + (() => resolveFrom(appiumHome, '@appium/fake-driver/package.json')).should.not.throw(); }); }); @@ -139,9 +126,7 @@ describe('CLI behavior', function () { let res; beforeEach(async function () { - res = /** @type {ExtensionListData} */ ( - await runJson([DRIVER_TYPE, LIST]) - ); + res = /** @type {ExtensionListData} */ (await runJson([DRIVER_TYPE, LIST])); }); it('should list the driver', function () { res.should.have.property('fake'); @@ -168,9 +153,7 @@ describe('CLI behavior', function () { }); it('should update package.json', async function () { - const newPkg = JSON.parse( - await fs.readFile(appiumHomePkgPath, 'utf8') - ); + const newPkg = JSON.parse(await fs.readFile(appiumHomePkgPath, 'utf8')); expect(newPkg).to.have.nested.property('devDependencies.test-driver'); }); @@ -188,8 +171,7 @@ describe('CLI behavior', function () { }); it('should actually install both drivers', function () { - expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to - .throw; + expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw; expect(() => resolveFrom(appiumHome, 'test-driver')).not.to.throw; }); }); @@ -234,21 +216,15 @@ describe('CLI behavior', function () { before(function () { const run = runAppiumJson(appiumHome); runInstall = async (args) => - /** @type {ReturnType} */ ( - await run([DRIVER_TYPE, INSTALL, ...args]) - ); + /** @type {ReturnType} */ (await run([DRIVER_TYPE, INSTALL, ...args])); runUninstall = async (args) => /** @type {ReturnType} */ ( await run([DRIVER_TYPE, UNINSTALL, ...args]) ); runList = async (args = []) => - /** @type {ReturnType} */ ( - await run([DRIVER_TYPE, LIST, ...args]) - ); + /** @type {ReturnType} */ (await run([DRIVER_TYPE, LIST, ...args])); runRun = async (args) => - /** @type {ReturnType} */ ( - await run([DRIVER_TYPE, RUN, ...args]) - ); + /** @type {ReturnType} */ (await run([DRIVER_TYPE, RUN, ...args])); }); describe(LIST, function () { @@ -290,14 +266,8 @@ describe('CLI behavior', function () { penultimateFakeDriverVersionAsOfRightNow ).should.be.true; // TODO: this could probably be replaced by looking at updateVersion in the JSON - const stdout = await runAppium(appiumHome, [ - DRIVER_TYPE, - LIST, - '--updates', - ]); - stdout.should.match( - new RegExp(`fake.+[${fake.updateVersion} available]`) - ); + const stdout = await runAppium(appiumHome, [DRIVER_TYPE, LIST, '--updates']); + stdout.should.match(new RegExp(`fake.+[${fake.updateVersion} available]`)); }); }); @@ -314,11 +284,7 @@ describe('CLI behavior', function () { }); it('should install a driver from npm', async function () { await clear(); - const ret = await runInstall([ - '@appium/fake-driver', - '--source', - 'npm', - ]); + const ret = await runInstall(['@appium/fake-driver', '--source', 'npm']); ret.fake.pkgName.should.eql('@appium/fake-driver'); ret.fake.installType.should.eql('npm'); ret.fake.installSpec.should.eql('@appium/fake-driver'); @@ -334,9 +300,7 @@ describe('CLI behavior', function () { const list = await runList(['--installed']); expect(list.fake).to.exist; expect(list.test).to.exist; - expect(() => - resolveFrom(appiumHome, '@appium/fake-driver') - ).not.to.throw; + expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw; expect(() => resolveFrom(appiumHome, 'test-driver')).not.to.throw; }); @@ -348,12 +312,8 @@ describe('CLI behavior', function () { const list = await runList(['--installed']); expect(list.fake).to.exist; expect(list.uiautomator2).to.exist; - expect(() => - resolveFrom(appiumHome, '@appium/fake-driver') - ).not.to.throw; - expect(() => - resolveFrom(appiumHome, 'appium-uiautomator2-driver') - ).not.to.throw; + expect(() => resolveFrom(appiumHome, '@appium/fake-driver')).not.to.throw; + expect(() => resolveFrom(appiumHome, 'appium-uiautomator2-driver')).not.to.throw; }); it('should install a driver from npm with a specific version/tag', async function () { @@ -411,9 +371,7 @@ describe('CLI behavior', function () { ]); ret.fake.pkgName.should.eql('appium-fake-driver'); ret.fake.installType.should.eql('git'); - ret.fake.installSpec.should.eql( - 'git+https://github.com/appium/appium-fake-driver' - ); + ret.fake.installSpec.should.eql('git+https://github.com/appium/appium-fake-driver'); const list = await runList(['--installed']); delete list.fake.installed; list.should.eql(ret); @@ -422,11 +380,7 @@ describe('CLI behavior', function () { await clear(); // take advantage of the fact that we know we have fake driver installed as a dependency in // this module, so we know its local path on disk - const ret = await installLocalExtension( - appiumHome, - DRIVER_TYPE, - FAKE_DRIVER_DIR - ); + const ret = await installLocalExtension(appiumHome, DRIVER_TYPE, FAKE_DRIVER_DIR); ret.fake.pkgName.should.eql('@appium/fake-driver'); ret.fake.installType.should.eql('local'); ret.fake.installSpec.should.eql(FAKE_DRIVER_DIR); @@ -446,11 +400,7 @@ describe('CLI behavior', function () { describe('uninstall', function () { it('should uninstall a driver based on its driver name', async function () { await clear(); - const ret = await runInstall([ - '@appium/fake-driver', - '--source', - 'npm', - ]); + const ret = await runInstall(['@appium/fake-driver', '--source', 'npm']); // this will throw if the file doesn't exist const installPath = resolveFrom(appiumHome, ret.fake.pkgName); let list = await runList(['--installed']); @@ -479,34 +429,24 @@ describe('CLI behavior', function () { }); it('should take a valid driver, invalid script, and throw an error', async function () { const driverName = 'fake'; - await expect( - runRun([driverName, 'foo']) - ).to.eventually.be.rejectedWith(Error); + await expect(runRun([driverName, 'foo'])).to.eventually.be.rejectedWith(Error); }); it('should take an invalid driver, invalid script, and throw an error', async function () { const driverName = 'foo'; - await expect( - runRun([driverName, 'bar']) - ).to.eventually.be.rejectedWith(Error); + await expect(runRun([driverName, 'bar'])).to.eventually.be.rejectedWith(Error); }); }); }); describe('Plugin CLI', function () { - const FAKE_PLUGIN_DIR = path.dirname( - require.resolve('@appium/fake-plugin/package.json') - ); + const FAKE_PLUGIN_DIR = path.dirname(require.resolve('@appium/fake-plugin/package.json')); before(function () { const run = runAppiumJson(appiumHome); runList = async (args = []) => - /** @type {ReturnType} */ ( - await run([PLUGIN_TYPE, LIST, ...args]) - ); + /** @type {ReturnType} */ (await run([PLUGIN_TYPE, LIST, ...args])); runRun = async (args) => - /** @type {ReturnType} */ ( - await run([PLUGIN_TYPE, RUN, ...args]) - ); + /** @type {ReturnType} */ (await run([PLUGIN_TYPE, RUN, ...args])); }); describe('run', function () { @@ -527,14 +467,10 @@ describe('CLI behavior', function () { }); it('should take a valid plugin, invalid script, and throw an error', async function () { const pluginName = 'fake'; - await expect( - runRun([pluginName, 'foo', '--json']) - ).to.eventually.be.rejectedWith(Error); + await expect(runRun([pluginName, 'foo', '--json'])).to.eventually.be.rejectedWith(Error); }); it('should take an invalid plugin, invalid script, and throw an error', async function () { - await expect( - runRun(['foo', 'bar', '--json']) - ).to.eventually.be.rejectedWith(Error); + await expect(runRun(['foo', 'bar', '--json'])).to.eventually.be.rejectedWith(Error); }); }); }); diff --git a/packages/appium/test/e2e/config-file.e2e.spec.js b/packages/appium/test/e2e/config-file.e2e.spec.js index 39bf06ecf..9b9ec7e8e 100644 --- a/packages/appium/test/e2e/config-file.e2e.spec.js +++ b/packages/appium/test/e2e/config-file.e2e.spec.js @@ -2,45 +2,23 @@ import {DRIVER_TYPE} from '../../lib/constants'; import {readConfigFile, normalizeConfig} from '../../lib/config-file'; -import { - finalizeSchema, - registerSchema, - resetSchema, -} from '../../lib/schema/schema'; +import {finalizeSchema, registerSchema, resetSchema} from '../../lib/schema/schema'; import extSchema from '../fixtures/driver.schema.js'; import {resolveFixture} from '../helpers'; describe('config file behavior', function () { const GOOD_FILEPATH = resolveFixture('config', 'appium.config.good.json'); - const BAD_NODECONFIG_FILEPATH = resolveFixture( - 'config', - 'appium.config.bad-nodeconfig.json' - ); + const BAD_NODECONFIG_FILEPATH = resolveFixture('config', 'appium.config.bad-nodeconfig.json'); const BAD_FILEPATH = resolveFixture('config', 'appium.config.bad.json'); - const INVALID_JSON_FILEPATH = resolveFixture( - 'config', - 'appium.config.invalid.json' - ); - const SECURITY_ARRAY_FILEPATH = resolveFixture( - 'config', - 'appium.config.security-array.json' - ); + const INVALID_JSON_FILEPATH = resolveFixture('config', 'appium.config.invalid.json'); + const SECURITY_ARRAY_FILEPATH = resolveFixture('config', 'appium.config.security-array.json'); const SECURITY_DELIMITED_FILEPATH = resolveFixture( 'config', 'appium.config.security-delimited.json' ); - const SECURITY_PATH_FILEPATH = resolveFixture( - 'config', - 'appium.config.security-path.json' - ); - const UNKNOWN_PROPS_FILEPATH = resolveFixture( - 'config', - 'appium.config.ext-unknown-props.json' - ); - const EXT_PROPS_FILEPATH = resolveFixture( - 'config', - 'appium.config.ext-good.json' - ); + const SECURITY_PATH_FILEPATH = resolveFixture('config', 'appium.config.security-path.json'); + const UNKNOWN_PROPS_FILEPATH = resolveFixture('config', 'appium.config.ext-unknown-props.json'); + const EXT_PROPS_FILEPATH = resolveFixture('config', 'appium.config.ext-good.json'); beforeEach(function () { finalizeSchema(); @@ -65,10 +43,7 @@ describe('config file behavior', function () { describe('when a string', function () { it('should return errors', async function () { const result = await readConfigFile(BAD_NODECONFIG_FILEPATH); - result.should.have.nested.property( - 'errors[0].instancePath', - '/server/nodeconfig' - ); + result.should.have.nested.property('errors[0].instancePath', '/server/nodeconfig'); }); }); @@ -84,20 +59,14 @@ describe('config file behavior', function () { describe('when a string path', function () { it('should return errors', async function () { const result = await readConfigFile(SECURITY_PATH_FILEPATH); - result.should.have.nested.property( - 'errors[0].instancePath', - '/server/allow-insecure' - ); + result.should.have.nested.property('errors[0].instancePath', '/server/allow-insecure'); }); }); describe('when a comma-delimited string', function () { it('should return errors', async function () { const result = await readConfigFile(SECURITY_DELIMITED_FILEPATH); - result.should.have.nested.property( - 'errors[0].instancePath', - '/server/allow-insecure' - ); + result.should.have.nested.property('errors[0].instancePath', '/server/allow-insecure'); }); }); @@ -118,10 +87,7 @@ describe('config file behavior', function () { describe('without extensions', function () { it('should return an object containing errors', async function () { const result = await readConfigFile(BAD_FILEPATH); - result.should.have.deep.property( - 'config', - normalizeConfig(require(BAD_FILEPATH)) - ); + result.should.have.deep.property('config', normalizeConfig(require(BAD_FILEPATH))); result.should.have.property('filepath', BAD_FILEPATH); result.should.have.deep .property('errors') @@ -147,8 +113,7 @@ describe('config file behavior', function () { }, { instancePath: '/server/allow-insecure', - schemaPath: - '#/properties/server/properties/allow-insecure/type', + schemaPath: '#/properties/server/properties/allow-insecure/type', keyword: 'type', params: { type: 'array', @@ -157,8 +122,7 @@ describe('config file behavior', function () { }, { instancePath: '/server/callback-port', - schemaPath: - '#/properties/server/properties/callback-port/maximum', + schemaPath: '#/properties/server/properties/callback-port/maximum', keyword: 'maximum', params: { comparison: '<=', diff --git a/packages/appium/test/e2e/config.e2e.spec.js b/packages/appium/test/e2e/config.e2e.spec.js index c12336316..f29729131 100644 --- a/packages/appium/test/e2e/config.e2e.spec.js +++ b/packages/appium/test/e2e/config.e2e.spec.js @@ -1,13 +1,7 @@ -import { createSandbox } from 'sinon'; -import { - getGitRev, - getBuildInfo, - updateBuildInfo, - APPIUM_VER, -} from '../../lib/config'; +import {createSandbox} from 'sinon'; +import {getGitRev, getBuildInfo, updateBuildInfo, APPIUM_VER} from '../../lib/config'; import axios from 'axios'; -import { fs } from '@appium/support'; - +import {fs} from '@appium/support'; describe('Config', function () { let sandbox; @@ -29,7 +23,7 @@ describe('Config', function () { }); }); describe('getBuildInfo', function () { - async function verifyBuildInfoUpdate (useLocalGit) { + async function verifyBuildInfoUpdate(useLocalGit) { const buildInfo = getBuildInfo(); mockFs.expects('exists').atLeast(1).returns(useLocalGit); buildInfo['git-sha'] = undefined; @@ -60,10 +54,8 @@ describe('Config', function () { data: [ { name: `v${APPIUM_VER}`, - zipball_url: - 'https://api.github.com/repos/appium/appium/zipball/v1.9.0-beta.1', - tarball_url: - 'https://api.github.com/repos/appium/appium/tarball/v1.9.0-beta.1', + zipball_url: 'https://api.github.com/repos/appium/appium/zipball/v1.9.0-beta.1', + tarball_url: 'https://api.github.com/repos/appium/appium/tarball/v1.9.0-beta.1', commit: { sha: '3c2752f9f9c56000705a4ae15b3ba68a5d2e644c', url: 'https://api.github.com/repos/appium/appium/commits/3c2752f9f9c56000705a4ae15b3ba68a5d2e644c', @@ -72,10 +64,8 @@ describe('Config', function () { }, { name: 'v1.8.2-beta', - zipball_url: - 'https://api.github.com/repos/appium/appium/zipball/v1.8.2-beta', - tarball_url: - 'https://api.github.com/repos/appium/appium/tarball/v1.8.2-beta', + zipball_url: 'https://api.github.com/repos/appium/appium/zipball/v1.8.2-beta', + tarball_url: 'https://api.github.com/repos/appium/appium/tarball/v1.8.2-beta', commit: { sha: '5b98b9197e75aa85e7507d21d3126c1a63d1ce8f', url: 'https://api.github.com/repos/appium/appium/commits/5b98b9197e75aa85e7507d21d3126c1a63d1ce8f', @@ -87,8 +77,7 @@ describe('Config', function () { getStub.onCall(1).returns({ data: { sha: '3c2752f9f9c56000705a4ae15b3ba68a5d2e644c', - node_id: - 'MDY6Q29tbWl0NzUzMDU3MDozYzI3NTJmOWY5YzU2MDAwNzA1YTRhZTE1YjNiYTY4YTVkMmU2NDRj', + node_id: 'MDY6Q29tbWl0NzUzMDU3MDozYzI3NTJmOWY5YzU2MDAwNzA1YTRhZTE1YjNiYTY4YTVkMmU2NDRj', commit: { author: { name: 'Isaac Murchie', diff --git a/packages/appium/test/e2e/driver.e2e.spec.js b/packages/appium/test/e2e/driver.e2e.spec.js index 07da29055..809030937 100644 --- a/packages/appium/test/e2e/driver.e2e.spec.js +++ b/packages/appium/test/e2e/driver.e2e.spec.js @@ -7,13 +7,7 @@ import axios from 'axios'; import {remote as wdio} from 'webdriverio'; import {main as appiumServer} from '../../lib/main'; import {INSTALL_TYPE_LOCAL} from '../../lib/extension/extension-config'; -import { - W3C_PREFIXED_CAPS, - TEST_FAKE_APP, - TEST_HOST, - getTestPort, - PROJECT_ROOT, -} from '../helpers'; +import {W3C_PREFIXED_CAPS, TEST_FAKE_APP, TEST_HOST, getTestPort, PROJECT_ROOT} from '../helpers'; import {BaseDriver} from '@appium/base-driver'; import {loadExtensions} from '../../lib/extension'; import {runExtensionCommand} from '../../lib/cli/extension'; @@ -127,9 +121,7 @@ describe('FakeDriver - via HTTP', function () { let driver = await wdio({...wdOpts, capabilities: caps}); const {sessionId} = driver; try { - const {data} = await axios.get( - `${testServerBaseSessionUrl}/${sessionId}/fakedriverargs` - ); + const {data} = await axios.get(`${testServerBaseSessionUrl}/${sessionId}/fakedriverargs`); should.not.exist(data.value.sillyWebServerPort); should.not.exist(data.value.sillyWebServerHost); } finally { @@ -153,9 +145,7 @@ describe('FakeDriver - via HTTP', function () { let driver = await wdio({...wdOpts, capabilities: caps}); const {sessionId} = driver; try { - const {data} = await axios.get( - `${testServerBaseSessionUrl}/${sessionId}/fakedriverargs` - ); + const {data} = await axios.get(`${testServerBaseSessionUrl}/${sessionId}/fakedriverargs`); data.value.sillyWebServerPort.should.eql(sillyWebServerPort); data.value.sillyWebServerHost.should.eql(sillyWebServerHost); } finally { @@ -206,9 +196,7 @@ describe('FakeDriver - via HTTP', function () { should.exist(driver.sessionId); await B.delay(250); - await driver - .getPageSource() - .should.eventually.be.rejectedWith(/terminated/); + await driver.getPageSource().should.eventually.be.rejectedWith(/terminated/); }); it('should accept valid W3C capabilities and start a W3C session', async function () { @@ -216,16 +204,12 @@ describe('FakeDriver - via HTTP', function () { const w3cCaps = { capabilities: { alwaysMatch: {'appium:automationName': 'Fake', platformName: 'Fake'}, - firstMatch: [ - {'appium:deviceName': 'Fake', 'appium:app': TEST_FAKE_APP}, - ], + firstMatch: [{'appium:deviceName': 'Fake', 'appium:app': TEST_FAKE_APP}], }, }; // Create the session - const {status, value, sessionId} = ( - await axios.post(testServerBaseSessionUrl, w3cCaps) - ).data; + const {status, value, sessionId} = (await axios.post(testServerBaseSessionUrl, w3cCaps)).data; try { should.not.exist(status); // Test that it's a W3C session by checking that 'status' is not in the response should.not.exist(sessionId); @@ -249,10 +233,10 @@ describe('FakeDriver - via HTTP', function () { // Now use that sessionID to call an arbitrary W3C-only endpoint that isn't implemented to see if it responds with correct error await axios - .post( - `${testServerBaseSessionUrl}/${value.sessionId}/execute/async`, - {script: '', args: ['a']} - ) + .post(`${testServerBaseSessionUrl}/${value.sessionId}/execute/async`, { + script: '', + args: ['a'], + }) .should.eventually.be.rejectedWith(/405/); } finally { // End session @@ -264,9 +248,7 @@ describe('FakeDriver - via HTTP', function () { const badW3Ccaps = { capabilities: { alwaysMatch: {}, - firstMatch: [ - {'appium:deviceName': 'Fake', 'appium:app': TEST_FAKE_APP}, - ], + firstMatch: [{'appium:deviceName': 'Fake', 'appium:app': TEST_FAKE_APP}], }, }; @@ -291,9 +273,8 @@ describe('FakeDriver - via HTTP', function () { }, }; - const {status, value, sessionId} = ( - await axios.post(testServerBaseSessionUrl, combinedCaps) - ).data; + const {status, value, sessionId} = (await axios.post(testServerBaseSessionUrl, combinedCaps)) + .data; try { should.not.exist(status); // If it's a W3C session, should not respond with 'status' should.not.exist(sessionId); @@ -317,9 +298,7 @@ describe('FakeDriver - via HTTP', function () { }, }, }; - await axios - .post(testServerBaseSessionUrl, w3cCaps) - .should.eventually.be.rejectedWith(/500/); + await axios.post(testServerBaseSessionUrl, w3cCaps).should.eventually.be.rejectedWith(/500/); }); it('should accept capabilities that are provided in the firstMatch array', async function () { @@ -334,9 +313,7 @@ describe('FakeDriver - via HTTP', function () { ], }, }; - const {value, sessionId, status} = ( - await axios.post(testServerBaseSessionUrl, w3cCaps) - ).data; + const {value, sessionId, status} = (await axios.post(testServerBaseSessionUrl, w3cCaps)).data; try { should.not.exist(status); should.not.exist(sessionId); @@ -387,10 +364,7 @@ describe('FakeDriver - via HTTP', function () { const createSessionStub = sandbox .stub(FakeDriver.prototype, 'createSession') .callsFake(async function (jsonwpCaps) { - const res = await BaseDriver.prototype.createSession.call( - this, - jsonwpCaps - ); + const res = await BaseDriver.prototype.createSession.call(this, jsonwpCaps); this.protocol.should.equal('MJSONWP'); return res; }); @@ -409,10 +383,9 @@ describe('FakeDriver - via HTTP', function () { let driver = await wdio({...wdOpts, capabilities: caps}); const {sessionId} = driver; try { - await axios.post( - `${testServerBaseSessionUrl}/${sessionId}/fakedriver`, - {thing: {yes: 'lolno'}} - ); + await axios.post(`${testServerBaseSessionUrl}/${sessionId}/fakedriver`, { + thing: {yes: 'lolno'}, + }); ( await axios.get(`${testServerBaseSessionUrl}/${sessionId}/fakedriver`) ).data.value.should.eql({yes: 'lolno'}); diff --git a/packages/appium/test/e2e/e2e-helpers.js b/packages/appium/test/e2e/e2e-helpers.js index 7df4db171..87f7d6a00 100644 --- a/packages/appium/test/e2e/e2e-helpers.js +++ b/packages/appium/test/e2e/e2e-helpers.js @@ -136,13 +136,7 @@ export const runAppiumJson = /** export async function installLocalExtension(appiumHome, type, pathToExtension) { return /** @type {import('appium/types').ExtRecord} */ ( /** @type {unknown} */ ( - await runAppiumJson(appiumHome, [ - type, - 'install', - '--source', - 'local', - pathToExtension, - ]) + await runAppiumJson(appiumHome, [type, 'install', '--source', 'local', pathToExtension]) ) ); } diff --git a/packages/appium/test/e2e/plugin.e2e.spec.js b/packages/appium/test/e2e/plugin.e2e.spec.js index 5fa25db42..bd3222175 100644 --- a/packages/appium/test/e2e/plugin.e2e.spec.js +++ b/packages/appium/test/e2e/plugin.e2e.spec.js @@ -7,12 +7,7 @@ import {remote as wdio} from 'webdriverio'; import axios from 'axios'; import {main as appiumServer} from '../../lib/main'; import {INSTALL_TYPE_LOCAL} from '../../lib/extension/extension-config'; -import { - W3C_PREFIXED_CAPS, - TEST_HOST, - getTestPort, - PROJECT_ROOT, -} from '../helpers'; +import {W3C_PREFIXED_CAPS, TEST_HOST, getTestPort, PROJECT_ROOT} from '../helpers'; import {runExtensionCommand} from '../../lib/cli/extension'; import {tempDir, fs} from '@appium/support'; import {loadExtensions} from '../../lib/extension'; @@ -29,12 +24,7 @@ const wdOpts = { capabilities: W3C_PREFIXED_CAPS, }; const FAKE_DRIVER_DIR = path.join(PROJECT_ROOT, 'packages', 'fake-driver'); -const FAKE_PLUGIN_DIR = path.join( - PROJECT_ROOT, - 'node_modules', - '@appium', - 'fake-plugin' -); +const FAKE_PLUGIN_DIR = path.join(PROJECT_ROOT, 'node_modules', '@appium', 'fake-plugin'); describe('FakePlugin', function () { /** @type {string} */ @@ -127,9 +117,7 @@ describe('FakePlugin', function () { }); it('should not update the server if plugin is not activated', async function () { - await axios - .post(`http://${TEST_HOST}:${port}/fake`) - .should.eventually.be.rejectedWith(/404/); + await axios.post(`http://${TEST_HOST}:${port}/fake`).should.eventually.be.rejectedWith(/404/); }); it('should not update method map if plugin is not activated', async function () { const driver = await wdio(wdOpts); @@ -167,8 +155,7 @@ describe('FakePlugin', function () { let server; before(async function () { // then start server if we need to - const usePlugins = - registrationType === 'explicit' ? ['fake', 'p2', 'p3'] : ['all']; + const usePlugins = registrationType === 'explicit' ? ['fake', 'p2', 'p3'] : ['all']; const args = { appiumHome, port, @@ -188,23 +175,18 @@ describe('FakePlugin', function () { }); it('should update the server', async function () { const res = {fake: 'fakeResponse'}; - (await axios.post(`http://${TEST_HOST}:${port}/fake`)).data.should.eql( - res - ); + (await axios.post(`http://${TEST_HOST}:${port}/fake`)).data.should.eql(res); }); it('should modify the method map with new commands', async function () { const driver = await wdio(wdOpts); const {sessionId} = driver; try { - await axios.post( - `${testServerBaseSessionUrl}/${sessionId}/fake_data`, - {data: {fake: 'data'}} - ); + await axios.post(`${testServerBaseSessionUrl}/${sessionId}/fake_data`, { + data: {fake: 'data'}, + }); ( - await axios.get( - `${testServerBaseSessionUrl}/${sessionId}/fake_data` - ) + await axios.get(`${testServerBaseSessionUrl}/${sessionId}/fake_data`) ).data.value.should.eql({fake: 'data'}); } finally { await driver.deleteSession(); @@ -217,9 +199,7 @@ describe('FakePlugin', function () { try { await driver .getPageSource() - .should.eventually.eql( - `${JSON.stringify([sessionId])}` - ); + .should.eventually.eql(`${JSON.stringify([sessionId])}`); } finally { await driver.deleteSession(); } @@ -230,10 +210,10 @@ describe('FakePlugin', function () { const {sessionId} = driver; try { const el = ( - await axios.post( - `${testServerBaseSessionUrl}/${sessionId}/element`, - {using: 'xpath', value: '//MockWebView'} - ) + await axios.post(`${testServerBaseSessionUrl}/${sessionId}/element`, { + using: 'xpath', + value: '//MockWebView', + }) ).data.value; el.should.have.property('fake'); } finally { @@ -248,11 +228,8 @@ describe('FakePlugin', function () { await axios.post(`${testServerBaseSessionUrl}/${sessionId}/context`, { name: 'PROXY', }); - const handle = ( - await axios.get( - `${testServerBaseSessionUrl}/${sessionId}/window/handle` - ) - ).data.value; + const handle = (await axios.get(`${testServerBaseSessionUrl}/${sessionId}/window/handle`)) + .data.value; handle.should.eql('<>'); } finally { await axios.post(`${testServerBaseSessionUrl}/${sessionId}/context`, { @@ -307,9 +284,7 @@ describe('FakePlugin', function () { const driver = await wdio(wdOpts); const {sessionId} = driver; try { - const {data} = await axios.get( - `${testServerBaseSessionUrl}/${sessionId}/fakepluginargs` - ); + const {data} = await axios.get(`${testServerBaseSessionUrl}/${sessionId}/fakepluginargs`); data.value.should.eql(FAKE_ARGS); } finally { await driver.deleteSession(); @@ -334,9 +309,7 @@ describe('FakePlugin', function () { const driver = await wdio(wdOpts); const {sessionId} = driver; try { - const {data} = await axios.get( - `${testServerBaseSessionUrl}/${sessionId}/fakepluginargs` - ); + const {data} = await axios.get(`${testServerBaseSessionUrl}/${sessionId}/fakepluginargs`); should.not.exist(data.value); } finally { await driver.deleteSession(); diff --git a/packages/appium/test/e2e/schema.e2e.spec.js b/packages/appium/test/e2e/schema.e2e.spec.js index 927cb6869..cbfd6acfe 100644 --- a/packages/appium/test/e2e/schema.e2e.spec.js +++ b/packages/appium/test/e2e/schema.e2e.spec.js @@ -1,9 +1,9 @@ // @ts-check -import { fs, tempDir } from '@appium/support'; +import {fs, tempDir} from '@appium/support'; import path from 'path'; -import { DRIVER_TYPE } from '../../lib/constants'; -import { resolveFixture } from '../helpers'; -import { installLocalExtension, runAppium } from './e2e-helpers'; +import {DRIVER_TYPE} from '../../lib/constants'; +import {resolveFixture} from '../helpers'; +import {installLocalExtension, runAppium} from './e2e-helpers'; const {expect} = chai; diff --git a/packages/appium/test/helpers.js b/packages/appium/test/helpers.js index 761b7d47f..1bb3ce38d 100644 --- a/packages/appium/test/helpers.js +++ b/packages/appium/test/helpers.js @@ -2,8 +2,8 @@ import getPort from 'get-port'; import path from 'path'; -import rewiremock, { addPlugin, overrideEntryPoint, plugins } from 'rewiremock'; -import { insertAppiumPrefixes } from '../lib/utils'; +import rewiremock, {addPlugin, overrideEntryPoint, plugins} from 'rewiremock'; +import {insertAppiumPrefixes} from '../lib/utils'; const TEST_HOST = '127.0.0.1'; @@ -16,7 +16,7 @@ const BASE_CAPS = { automationName: 'Fake', platformName: 'Fake', deviceName: 'Fake', - app: TEST_FAKE_APP + app: TEST_FAKE_APP, }; const W3C_PREFIXED_CAPS = {...insertAppiumPrefixes(BASE_CAPS)}; /** @type {import('@appium/types').W3CCapabilities} */ @@ -30,7 +30,7 @@ let TEST_PORT; * Returns a free port; one per process * @returns {Promise} a free port */ -async function getTestPort () { +async function getTestPort() { return await (TEST_PORT || getPort()); } @@ -40,11 +40,23 @@ async function getTestPort () { * @param {...string} pathParts - Additional paths to `join()` * @returns {string} */ -function resolveFixture (filename, ...pathParts) { +function resolveFixture(filename, ...pathParts) { return path.join(__dirname, 'fixtures', filename, ...pathParts); } overrideEntryPoint(module); addPlugin(plugins.nodejs); -export { TEST_FAKE_APP, TEST_HOST, BASE_CAPS, W3C_PREFIXED_CAPS, W3C_CAPS, PROJECT_ROOT, getTestPort, rewiremock, resolveFixture, FAKE_DRIVER_DIR, PACKAGE_ROOT }; +export { + TEST_FAKE_APP, + TEST_HOST, + BASE_CAPS, + W3C_PREFIXED_CAPS, + W3C_CAPS, + PROJECT_ROOT, + getTestPort, + rewiremock, + resolveFixture, + FAKE_DRIVER_DIR, + PACKAGE_ROOT, +}; diff --git a/packages/appium/test/unit/appiumdriver.spec.js b/packages/appium/test/unit/appiumdriver.spec.js index 92c18687e..315a1f65c 100644 --- a/packages/appium/test/unit/appiumdriver.spec.js +++ b/packages/appium/test/unit/appiumdriver.spec.js @@ -1,15 +1,15 @@ // @ts-check -import { PLUGIN_TYPE } from '../../lib/constants'; +import {PLUGIN_TYPE} from '../../lib/constants'; import B from 'bluebird'; -import { BaseDriver } from '@appium/base-driver'; -import { FakeDriver } from '@appium/fake-driver'; -import { sleep } from 'asyncbox'; +import {BaseDriver} from '@appium/base-driver'; +import {FakeDriver} from '@appium/fake-driver'; +import {sleep} from 'asyncbox'; import _ from 'lodash'; -import { createSandbox } from 'sinon'; -import { finalizeSchema, registerSchema, resetSchema } from '../../lib/schema/schema'; -import { insertAppiumPrefixes, removeAppiumPrefixes } from '../../lib/utils'; -import { rewiremock, BASE_CAPS, W3C_CAPS, W3C_PREFIXED_CAPS } from '../helpers'; +import {createSandbox} from 'sinon'; +import {finalizeSchema, registerSchema, resetSchema} from '../../lib/schema/schema'; +import {insertAppiumPrefixes, removeAppiumPrefixes} from '../../lib/utils'; +import {rewiremock, BASE_CAPS, W3C_CAPS, W3C_PREFIXED_CAPS} from '../helpers'; const SESSION_ID = '1'; @@ -28,13 +28,13 @@ describe('AppiumDriver', function () { MockConfig = { getBuildInfo: sandbox.stub().returns({ - version: '2.0' + version: '2.0', }), updateBuildInfo: sandbox.stub().resolves(), - APPIUM_VER: '2.0' + APPIUM_VER: '2.0', }; ({AppiumDriver} = rewiremock.proxy(() => require('../../lib/appium'), { - '../../lib/config': MockConfig + '../../lib/config': MockConfig, })); }); @@ -75,11 +75,11 @@ describe('AppiumDriver', function () { * @param {*} DriverClass * @returns {[AppiumDriver, sinon.SinonMock]} */ - function getDriverAndFakeDriver (appiumArgs = {}, DriverClass = FakeDriver) { + function getDriverAndFakeDriver(appiumArgs = {}, DriverClass = FakeDriver) { const appium = new AppiumDriver(appiumArgs); fakeDriver = new DriverClass(); const mockFakeDriver = sandbox.mock(fakeDriver); - const mockedDriverReturnerClass = function Driver () { + const mockedDriverReturnerClass = function Driver() { return fakeDriver; }; @@ -88,7 +88,7 @@ describe('AppiumDriver', function () { driver: mockedDriverReturnerClass, version: '1.2.3', driverName: 'fake', - }) + }), }; return [appium, mockFakeDriver]; @@ -107,18 +107,25 @@ describe('AppiumDriver', function () { }); it(`should call inner driver's createSession with desired capabilities`, async function () { - mockFakeDriver.expects('createSession') - .once().withExactArgs(undefined, null, W3C_CAPS, []) + mockFakeDriver + .expects('createSession') + .once() + .withExactArgs(undefined, null, W3C_CAPS, []) .returns([SESSION_ID, removeAppiumPrefixes(W3C_PREFIXED_CAPS)]); await appium.createSession(undefined, null, W3C_CAPS); mockFakeDriver.verify(); }); it(`should call inner driver's createSession with desired and default capabilities`, async function () { let defaultCaps = {'appium:someCap': 'hello'}; - let allCaps = {...W3C_CAPS, alwaysMatch: {...W3C_CAPS.alwaysMatch, ...defaultCaps}}; + let allCaps = { + ...W3C_CAPS, + alwaysMatch: {...W3C_CAPS.alwaysMatch, ...defaultCaps}, + }; appium.args.defaultCapabilities = defaultCaps; - mockFakeDriver.expects('createSession') - .once().withArgs(undefined, null, allCaps) + mockFakeDriver + .expects('createSession') + .once() + .withArgs(undefined, null, allCaps) .returns([SESSION_ID, removeAppiumPrefixes(allCaps.alwaysMatch)]); await appium.createSession(undefined, null, W3C_CAPS); mockFakeDriver.verify(); @@ -128,8 +135,10 @@ describe('AppiumDriver', function () { // should do nothing let defaultCaps = {platformName: 'Ersatz'}; appium.args.defaultCapabilities = defaultCaps; - mockFakeDriver.expects('createSession') - .once().withArgs(undefined, null, W3C_CAPS) + mockFakeDriver + .expects('createSession') + .once() + .withArgs(undefined, null, W3C_CAPS) .returns([SESSION_ID, removeAppiumPrefixes(W3C_PREFIXED_CAPS)]); await appium.createSession(undefined, null, W3C_CAPS); mockFakeDriver.verify(); @@ -138,19 +147,14 @@ describe('AppiumDriver', function () { appium.args.sessionOverride = true; // mock three sessions that should be removed when the new one is created - let fakeDrivers = [ - new FakeDriver(), - new FakeDriver(), - new FakeDriver(), - ]; + let fakeDrivers = [new FakeDriver(), new FakeDriver(), new FakeDriver()]; let mockFakeDrivers = _.map(fakeDrivers, (fd) => sandbox.mock(fd)); - mockFakeDrivers[0].expects('deleteSession') - .once(); - mockFakeDrivers[1].expects('deleteSession') + mockFakeDrivers[0].expects('deleteSession').once(); + mockFakeDrivers[1] + .expects('deleteSession') .once() .throws('Cannot shut down Android driver; it has already shut down'); - mockFakeDrivers[2].expects('deleteSession') - .once(); + mockFakeDrivers[2].expects('deleteSession').once(); appium.sessions['abc-123-xyz'] = fakeDrivers[0]; appium.sessions['xyz-321-abc'] = fakeDrivers[1]; appium.sessions['123-abc-xyz'] = fakeDrivers[2]; @@ -158,8 +162,10 @@ describe('AppiumDriver', function () { let sessions = await appium.getSessions(); sessions.should.have.length(3); - mockFakeDriver.expects('createSession') - .once().withExactArgs(undefined, null, W3C_CAPS, []) + mockFakeDriver + .expects('createSession') + .once() + .withExactArgs(undefined, null, W3C_CAPS, []) .returns([SESSION_ID, removeAppiumPrefixes(W3C_PREFIXED_CAPS)]); await appium.createSession(undefined, null, W3C_CAPS); @@ -172,8 +178,10 @@ describe('AppiumDriver', function () { mockFakeDriver.verify(); }); it('should call "createSession" with W3C capabilities argument, if provided', async function () { - mockFakeDriver.expects('createSession') - .once().withArgs(undefined, undefined, W3C_CAPS) + mockFakeDriver + .expects('createSession') + .once() + .withArgs(undefined, undefined, W3C_CAPS) .returns([SESSION_ID, BASE_CAPS]); await appium.createSession(undefined, undefined, W3C_CAPS); mockFakeDriver.verify(); @@ -187,8 +195,10 @@ describe('AppiumDriver', function () { 'appium:someOtherParm': 'someOtherParm', }, }; - mockFakeDriver.expects('createSession') - .once().withArgs(undefined, undefined, { + mockFakeDriver + .expects('createSession') + .once() + .withArgs(undefined, undefined, { alwaysMatch: { ...w3cCaps.alwaysMatch, 'appium:someOtherParm': 'someOtherParm', @@ -220,8 +230,7 @@ describe('AppiumDriver', function () { }); it('should assign args to property `cliArgs`', async function () { - class ArgsDriver extends BaseDriver { - } + class ArgsDriver extends BaseDriver {} const args = {driver: {fake: {randomArg: 1234}}}; [appium, mockFakeDriver] = getDriverAndFakeDriver(args, ArgsDriver); const {value} = await appium.createSession(undefined, undefined, W3C_CAPS); @@ -249,11 +258,9 @@ describe('AppiumDriver', function () { sessions = await appium.getSessions(); sessions.should.have.length(0); }); - it('should call inner driver\'s deleteSession method', async function () { + it("should call inner driver's deleteSession method", async function () { const [sessionId] = (await appium.createSession(null, null, W3C_CAPS)).value; - mockFakeDriver.expects('deleteSession') - .once().withExactArgs(sessionId, []) - .returns(); + mockFakeDriver.expects('deleteSession').once().withExactArgs(sessionId, []).returns(); await appium.deleteSession(sessionId); mockFakeDriver.verify(); @@ -279,12 +286,20 @@ describe('AppiumDriver', function () { sessions.should.be.empty; }); it('should return sessions created', async function () { - let caps1 = {alwaysMatch: {...W3C_PREFIXED_CAPS, 'appium:cap': 'value'}}; - let caps2 = {alwaysMatch: {...W3C_PREFIXED_CAPS, 'appium:cap': 'other value'}}; - mockFakeDriver.expects('createSession').once() + let caps1 = { + alwaysMatch: {...W3C_PREFIXED_CAPS, 'appium:cap': 'value'}, + }; + let caps2 = { + alwaysMatch: {...W3C_PREFIXED_CAPS, 'appium:cap': 'other value'}, + }; + mockFakeDriver + .expects('createSession') + .once() .returns(['fake-session-id-1', removeAppiumPrefixes(caps1.alwaysMatch)]); let [session1Id, session1Caps] = (await appium.createSession(null, null, caps1)).value; - mockFakeDriver.expects('createSession').once() + mockFakeDriver + .expects('createSession') + .once() .returns(['fake-session-id-2', removeAppiumPrefixes(caps2.alwaysMatch)]); let [session2Id, session2Caps] = (await appium.createSession(null, null, caps2)).value; @@ -308,8 +323,7 @@ describe('AppiumDriver', function () { status.build.version.should.exist; }); }); - describe('sessionExists', function () { - }); + describe('sessionExists', function () {}); describe('attachUnexpectedShutdownHandler', function () { /** @type {AppiumDriver} */ let appium; @@ -324,7 +338,7 @@ describe('AppiumDriver', function () { }); it('should remove session if inner driver unexpectedly exits with an error', async function () { - let [sessionId,] = (await appium.createSession(null, null, _.clone(W3C_CAPS))).value; // eslint-disable-line comma-spacing + let [sessionId] = (await appium.createSession(null, null, _.clone(W3C_CAPS))).value; // eslint-disable-line comma-spacing _.keys(appium.sessions).should.contain(sessionId); appium.sessions[sessionId].eventEmitter.emit('onUnexpectedShutdown', new Error('Oops')); // let event loop spin so rejection is handled @@ -332,7 +346,7 @@ describe('AppiumDriver', function () { _.keys(appium.sessions).should.not.contain(sessionId); }); it('should remove session if inner driver unexpectedly exits with no error', async function () { - let [sessionId,] = (await appium.createSession(null, null, _.clone(W3C_CAPS))).value; // eslint-disable-line comma-spacing + let [sessionId] = (await appium.createSession(null, null, _.clone(W3C_CAPS))).value; // eslint-disable-line comma-spacing _.keys(appium.sessions).should.contain(sessionId); appium.sessions[sessionId].eventEmitter.emit('onUnexpectedShutdown'); // let event loop spin so rejection is handled @@ -363,18 +377,18 @@ describe('AppiumDriver', function () { properties: { randomArg: { type: 'number', - default: 2000 - } - } + default: 2000, + }, + }, }); registerSchema(PLUGIN_TYPE, ArrayArgPlugin.pluginName, { type: 'object', properties: { arr: { type: 'array', - default: [] - } - } + default: [], + }, + }, }); finalizeSchema(); }); @@ -391,7 +405,9 @@ describe('AppiumDriver', function () { describe('when args are equal to the schema defaults', function () { it('should not set CLI args', function () { - const appium = new AppiumDriver({plugin: {[ArgsPlugin.pluginName]: {randomArg: 2000}}}); + const appium = new AppiumDriver({ + plugin: {[ArgsPlugin.pluginName]: {randomArg: 2000}}, + }); appium.pluginClasses = [NoArgsPlugin, ArgsPlugin]; for (const plugin of appium.createPluginInstances()) { chai.expect(plugin.cliArgs).not.to.exist; @@ -400,7 +416,9 @@ describe('AppiumDriver', function () { describe('when the default is an "object"', function () { it('should not set CLI args', function () { - const appium = new AppiumDriver({plugin: {[ArrayArgPlugin.pluginName]: {arr: []}}}); + const appium = new AppiumDriver({ + plugin: {[ArrayArgPlugin.pluginName]: {arr: []}}, + }); appium.pluginClasses = [NoArgsPlugin, ArgsPlugin, ArrayArgPlugin]; for (const plugin of appium.createPluginInstances()) { chai.expect(plugin.cliArgs).not.to.exist; diff --git a/packages/appium/test/unit/cli/cli.spec.js b/packages/appium/test/unit/cli/cli.spec.js index 14778a42c..f53581c3e 100644 --- a/packages/appium/test/unit/cli/cli.spec.js +++ b/packages/appium/test/unit/cli/cli.spec.js @@ -1,9 +1,8 @@ - -import { tempDir, fs, npm } from '@appium/support'; -import { loadExtensions } from '../../../lib/extension'; -import { Manifest } from '../../../lib/extension/manifest'; +import {tempDir, fs, npm} from '@appium/support'; +import {loadExtensions} from '../../../lib/extension'; +import {Manifest} from '../../../lib/extension/manifest'; import DriverCommand from '../../../lib/cli/driver-command'; -import { createSandbox } from 'sinon'; +import {createSandbox} from 'sinon'; describe('DriverCommand', function () { /** @@ -40,12 +39,14 @@ describe('DriverCommand', function () { npmMock = sandbox.mock(npm); }); - function setupDriverUpdate (curVersion, latestVersion, latestSafeVersion) { - npmMock.expects('getLatestVersion') + function setupDriverUpdate(curVersion, latestVersion, latestSafeVersion) { + npmMock + .expects('getLatestVersion') .once() .withExactArgs(appiumHome, pkgName) .returns(latestVersion); - npmMock.expects('getLatestSafeUpgradeVersion') + npmMock + .expects('getLatestSafeUpgradeVersion') .once() .withExactArgs(appiumHome, pkgName, curVersion) .returns(latestSafeVersion); diff --git a/packages/appium/test/unit/cli/schema-args.spec.js b/packages/appium/test/unit/cli/schema-args.spec.js index a1df210a2..e1ebbdccc 100644 --- a/packages/appium/test/unit/cli/schema-args.spec.js +++ b/packages/appium/test/unit/cli/schema-args.spec.js @@ -1,9 +1,5 @@ import {createSandbox} from 'sinon'; -import { - finalizeSchema, - resetSchema, - SchemaFinalizationError, -} from '../../../lib/schema/schema'; +import {finalizeSchema, resetSchema, SchemaFinalizationError} from '../../../lib/schema/schema'; import {rewiremock} from '../../helpers'; const expect = chai.expect; @@ -19,9 +15,7 @@ describe('cli/schema-args', function () { beforeEach(function () { sandbox = createSandbox(); - ({toParserArgs} = rewiremock.proxy(() => - require('../../../lib/schema/cli-args') - )); + ({toParserArgs} = rewiremock.proxy(() => require('../../../lib/schema/cli-args'))); }); afterEach(function () { @@ -35,10 +29,7 @@ describe('cli/schema-args', function () { afterEach(resetSchema); it('should return a Map', function () { - expect(toParserArgs()) - .to.be.an.instanceof(Map) - .and.have.property('size') - .that.is.above(0); + expect(toParserArgs()).to.be.an.instanceof(Map).and.have.property('size').that.is.above(0); }); it('should generate metavars in SCREAMING_SNAKE_CASE', function () { @@ -46,9 +37,7 @@ describe('cli/schema-args', function () { const argDefsWithMetavar = [...argDefs].filter((arg) => arg[1].metavar); expect(argDefsWithMetavar).not.to.be.empty; // is there a more idiomatic way to do this? - expect( - argDefsWithMetavar.every((arg) => /[A-Z_]+/.test(arg[1].metavar)) - ).to.be.true; + expect(argDefsWithMetavar.every((arg) => /[A-Z_]+/.test(arg[1].metavar))).to.be.true; }); }); diff --git a/packages/appium/test/unit/config-file.spec.js b/packages/appium/test/unit/config-file.spec.js index 4441c7afd..2e18268c2 100644 --- a/packages/appium/test/unit/config-file.spec.js +++ b/packages/appium/test/unit/config-file.spec.js @@ -9,26 +9,12 @@ import {resolveFixture, rewiremock} from '../helpers'; const expect = chai.expect; describe('config-file', function () { - const GOOD_YAML_CONFIG_FILEPATH = resolveFixture( - 'config', - 'appium.config.good.yaml' - ); - const GOOD_JSON_CONFIG_FILEPATH = resolveFixture( - 'config', - 'appium.config.good.json' - ); - const GOOD_JS_CONFIG_FILEPATH = resolveFixture( - 'config', - 'appium.config.good.js' - ); - const GOOD_YAML_CONFIG = YAML.parse( - fs.readFileSync(GOOD_YAML_CONFIG_FILEPATH, 'utf8') - ); + const GOOD_YAML_CONFIG_FILEPATH = resolveFixture('config', 'appium.config.good.yaml'); + const GOOD_JSON_CONFIG_FILEPATH = resolveFixture('config', 'appium.config.good.json'); + const GOOD_JS_CONFIG_FILEPATH = resolveFixture('config', 'appium.config.good.js'); + const GOOD_YAML_CONFIG = YAML.parse(fs.readFileSync(GOOD_YAML_CONFIG_FILEPATH, 'utf8')); const GOOD_JSON_CONFIG = require(GOOD_JSON_CONFIG_FILEPATH); - const BAD_JSON_CONFIG_FILEPATH = resolveFixture( - 'config', - 'appium.config.bad.json' - ); + const BAD_JSON_CONFIG_FILEPATH = resolveFixture('config', 'appium.config.bad.json'); const BAD_JSON_CONFIG = require(BAD_JSON_CONFIG_FILEPATH); /** @@ -192,9 +178,7 @@ describe('config-file', function () { describe('when the config file is not empty', function () { it('should validate the config against a schema', function () { - expect(schema.validate).to.have.been.calledOnceWith( - GOOD_JSON_CONFIG - ); + expect(schema.validate).to.have.been.calledOnceWith(GOOD_JSON_CONFIG); }); describe('when the config file is valid', function () { @@ -274,9 +258,7 @@ describe('config-file', function () { }); it('should pass error through', async function () { - await expect(readConfigFile('appium.json')).to.be.rejectedWith( - /guru meditation/ - ); + await expect(readConfigFile('appium.json')).to.be.rejectedWith(/guru meditation/); }); }); @@ -298,9 +280,7 @@ describe('config-file', function () { describe('when the config file is not empty', function () { it('should validate the config against a schema', function () { - expect(schema.validate).to.have.been.calledOnceWith( - GOOD_JSON_CONFIG - ); + expect(schema.validate).to.have.been.calledOnceWith(GOOD_JSON_CONFIG); }); describe('when the config file is valid', function () { @@ -334,19 +314,13 @@ describe('config-file', function () { describe('formatErrors()', function () { describe('when provided `errors` as an empty array', function () { it('should throw', function () { - expect(() => formatErrors([])).to.throw( - TypeError, - 'Array of errors must be non-empty' - ); + expect(() => formatErrors([])).to.throw(TypeError, 'Array of errors must be non-empty'); }); }); describe('when provided `errors` as `undefined`', function () { it('should throw', function () { - expect(() => formatErrors()).to.throw( - TypeError, - 'Array of errors must be non-empty' - ); + expect(() => formatErrors()).to.throw(TypeError, 'Array of errors must be non-empty'); }); }); diff --git a/packages/appium/test/unit/config.spec.js b/packages/appium/test/unit/config.spec.js index 0b6f03e1b..4e9b44998 100644 --- a/packages/appium/test/unit/config.spec.js +++ b/packages/appium/test/unit/config.spec.js @@ -1,12 +1,25 @@ // @ts-check import _ from 'lodash'; -import { createSandbox } from 'sinon'; -import { getParser } from '../../lib/cli/parser'; -import { checkNodeOk, getBuildInfo, getNonDefaultServerArgs, showBuildInfo, showConfig, validateTmpDir, warnNodeDeprecations } from '../../lib/config'; -import { PLUGIN_TYPE } from '../../lib/constants'; +import {createSandbox} from 'sinon'; +import {getParser} from '../../lib/cli/parser'; +import { + checkNodeOk, + getBuildInfo, + getNonDefaultServerArgs, + showBuildInfo, + showConfig, + validateTmpDir, + warnNodeDeprecations, +} from '../../lib/config'; +import {PLUGIN_TYPE} from '../../lib/constants'; import logger from '../../lib/logger'; -import { finalizeSchema, getDefaultsForSchema, registerSchema, resetSchema } from '../../lib/schema/schema'; +import { + finalizeSchema, + getDefaultsForSchema, + registerSchema, + resetSchema, +} from '../../lib/schema/schema'; describe('Config', function () { /** @type {sinon.SinonSandbox} */ @@ -44,9 +57,11 @@ describe('Config', function () { it('should dump the current Appium config', function () { showConfig( {address: 'bar'}, - {config: { - // @ts-expect-error - server: {callbackAddress: 'quux'}} + { + config: { + // @ts-expect-error + server: {callbackAddress: 'quux'}, + }, }, {port: 1234}, {allowCors: false} @@ -103,9 +118,17 @@ describe('Config', function () { describe('checkNodeOk', function () { describe('unsupported nodes', function () { const unsupportedVersions = [ - 'v0.1', 'v0.9.12', 'v0.10.36', 'v0.12.14', - 'v4.4.7', 'v5.7.0', 'v6.3.1', 'v7.1.1', - 'v8.0.0', 'v9.2.3', 'v10.1.0', + 'v0.1', + 'v0.9.12', + 'v0.10.36', + 'v0.12.14', + 'v4.4.7', + 'v5.7.0', + 'v6.3.1', + 'v7.1.1', + 'v8.0.0', + 'v9.2.3', + 'v10.1.0', ]; for (const version of unsupportedVersions) { it(`should fail if node is ${version}`, function () { @@ -188,7 +211,10 @@ describe('Config', function () { describe('with extension schemas', function () { beforeEach(function () { resetSchema(); - registerSchema(PLUGIN_TYPE, 'crypto-fiend', {type: 'object', properties: {elite: {type: 'boolean', default: true}}}); + registerSchema(PLUGIN_TYPE, 'crypto-fiend', { + type: 'object', + properties: {elite: {type: 'boolean', default: true}}, + }); finalizeSchema(); getParser(true); args = getDefaultsForSchema(); @@ -210,7 +236,9 @@ describe('Config', function () { describe('validateTmpDir', function () { it('should fail to use a tmp dir with incorrect permissions', function () { - validateTmpDir('/private/if_you_run_with_sudo_this_wont_fail').should.be.rejectedWith(/could not ensure/); + validateTmpDir('/private/if_you_run_with_sudo_this_wont_fail').should.be.rejectedWith( + /could not ensure/ + ); }); it('should fail to use an undefined tmp dir', function () { // @ts-expect-error diff --git a/packages/appium/test/unit/extension/driver-config.spec.js b/packages/appium/test/unit/extension/driver-config.spec.js index f5b9fb901..35fccb173 100644 --- a/packages/appium/test/unit/extension/driver-config.spec.js +++ b/packages/appium/test/unit/extension/driver-config.spec.js @@ -317,18 +317,17 @@ describe('DriverConfig', function () { describe('when the extension data is missing `schema`', function () { it('should throw', function () { delete extData.schema; - expect(() => - driverConfig.readExtensionSchema(extName, extData) - ).to.throw(TypeError, /why is this function being called/i); + expect(() => driverConfig.readExtensionSchema(extName, extData)).to.throw( + TypeError, + /why is this function being called/i + ); }); }); describe('when the extension schema has already been registered (with the same schema)', function () { it('should not throw', function () { driverConfig.readExtensionSchema(extName, extData); - expect(() => - driverConfig.readExtensionSchema(extName, extData) - ).not.to.throw(); + expect(() => driverConfig.readExtensionSchema(extName, extData)).not.to.throw(); }); }); diff --git a/packages/appium/test/unit/extension/manifest.spec.js b/packages/appium/test/unit/extension/manifest.spec.js index 06eedbcb0..c9f83260a 100644 --- a/packages/appium/test/unit/extension/manifest.spec.js +++ b/packages/appium/test/unit/extension/manifest.spec.js @@ -36,10 +36,7 @@ describe('Manifest', function () { ({MockPackageChanged, MockAppiumSupport, overrides, sandbox} = initMocks()); MockAppiumSupport.fs.readFile.resolves(yamlFixture); - ({Manifest} = rewiremock.proxy( - () => require('../../../lib/extension/manifest'), - overrides - )); + ({Manifest} = rewiremock.proxy(() => require('../../../lib/extension/manifest'), overrides)); Manifest.getInstance.cache = new Map(); }); @@ -71,9 +68,7 @@ describe('Manifest', function () { describe('property', function () { describe('appiumHome', function () { it('should return the `appiumHome` path', function () { - expect(Manifest.getInstance('/some/path').appiumHome).to.equal( - '/some/path' - ); + expect(Manifest.getInstance('/some/path').appiumHome).to.equal('/some/path'); }); it('should not be writable', function () { @@ -88,8 +83,7 @@ describe('Manifest', function () { describe('manifestPath', function () { describe('before `read()` has been called', function () { it('should be undefined', function () { - expect(Manifest.getInstance('/some/path').manifestPath).to.be - .undefined; + expect(Manifest.getInstance('/some/path').manifestPath).to.be.undefined; }); }); @@ -348,10 +342,7 @@ describe('Manifest', function () { }); it('should add an extension to the internal data', function () { - manifest.addExtensionFromPackage( - packageJson, - '/some/path/to/package.json' - ); + manifest.addExtensionFromPackage(packageJson, '/some/path/to/package.json'); expect(manifest.getExtensionData('driver')).to.deep.equal({ myDriver: { automationName: 'derp', @@ -366,29 +357,18 @@ describe('Manifest', function () { }); it('should return `true`', function () { - expect( - manifest.addExtensionFromPackage( - packageJson, - '/some/path/to/package.json' - ) - ).to.be.true; + expect(manifest.addExtensionFromPackage(packageJson, '/some/path/to/package.json')).to.be + .true; }); describe('when the driver has already been registered', function () { beforeEach(function () { - manifest.addExtensionFromPackage( - packageJson, - '/some/path/to/package.json' - ); + manifest.addExtensionFromPackage(packageJson, '/some/path/to/package.json'); }); it('should return `false`', function () { - expect( - manifest.addExtensionFromPackage( - packageJson, - '/some/path/to/package.json' - ) - ).to.be.false; + expect(manifest.addExtensionFromPackage(packageJson, '/some/path/to/package.json')).to + .be.false; }); }); }); @@ -408,10 +388,7 @@ describe('Manifest', function () { }); it('should add an extension to the internal data', function () { - manifest.addExtensionFromPackage( - packageJson, - '/some/path/to/package.json' - ); + manifest.addExtensionFromPackage(packageJson, '/some/path/to/package.json'); expect(manifest.getExtensionData(PLUGIN_TYPE)).to.deep.equal({ myPlugin: { mainClass: 'SomeClass', @@ -424,29 +401,18 @@ describe('Manifest', function () { }); it('should return `true`', function () { - expect( - manifest.addExtensionFromPackage( - packageJson, - '/some/path/to/package.json' - ) - ).to.be.true; + expect(manifest.addExtensionFromPackage(packageJson, '/some/path/to/package.json')).to.be + .true; }); describe('when the plugin has already been registered', function () { beforeEach(function () { - manifest.addExtensionFromPackage( - packageJson, - '/some/path/to/package.json' - ); + manifest.addExtensionFromPackage(packageJson, '/some/path/to/package.json'); }); it('should return `false`', function () { - expect( - manifest.addExtensionFromPackage( - packageJson, - '/some/path/to/package.json' - ) - ).to.be.false; + expect(manifest.addExtensionFromPackage(packageJson, '/some/path/to/package.json')).to + .be.false; }); }); }); @@ -504,9 +470,7 @@ describe('Manifest', function () { it('should add a found extension', async function () { await manifest.syncWithInstalledExtensions(); - expect(manifest.getExtensionData(DRIVER_TYPE)).to.have.property( - 'myDriver' - ); + expect(manifest.getExtensionData(DRIVER_TYPE)).to.have.property('myDriver'); }); }); diff --git a/packages/appium/test/unit/extension/mocks.js b/packages/appium/test/unit/extension/mocks.js index 628d5f442..18de60a63 100644 --- a/packages/appium/test/unit/extension/mocks.js +++ b/packages/appium/test/unit/extension/mocks.js @@ -6,21 +6,17 @@ */ import path from 'path'; -import { createSandbox } from 'sinon'; +import {createSandbox} from 'sinon'; -export function initMocks (sandbox = createSandbox()) { +export function initMocks(sandbox = createSandbox()) { /** * Mocks for package `@appium/support` * @type {MockAppiumSupport} */ const MockAppiumSupport = { fs: { - readFile: /** @type {MockAppiumSupportFs['readFile']} */ ( - sandbox.stub().resolves('{}') - ), - writeFile: /** @type {MockAppiumSupportFs['writeFile']} */ ( - sandbox.stub().resolves(true) - ), + readFile: /** @type {MockAppiumSupportFs['readFile']} */ (sandbox.stub().resolves('{}')), + writeFile: /** @type {MockAppiumSupportFs['writeFile']} */ (sandbox.stub().resolves(true)), walk: /** @type {MockAppiumSupportFs['walk']} */ ( sandbox.stub().returns({ [Symbol.asyncIterator]: sandbox @@ -28,28 +24,20 @@ export function initMocks (sandbox = createSandbox()) { .returns({next: sandbox.stub().resolves({done: true})}), }) ), - mkdirp: /** @type {MockAppiumSupportFs['mkdirp']} */ ( - sandbox.stub().resolves() - ), + mkdirp: /** @type {MockAppiumSupportFs['mkdirp']} */ (sandbox.stub().resolves()), }, env: { - resolveAppiumHome: - /** @type {MockAppiumSupportEnv['resolveAppiumHome']} */ ( + resolveAppiumHome: /** @type {MockAppiumSupportEnv['resolveAppiumHome']} */ ( sandbox.stub().resolves('/some/path') ), - resolveManifestPath: - /** @type {MockAppiumSupportEnv['resolveManifestPath']} */ ( + resolveManifestPath: /** @type {MockAppiumSupportEnv['resolveManifestPath']} */ ( sandbox.stub().resolves('/some/path/extensions.yaml') ), - hasAppiumDependency: - /** @type {MockAppiumSupportEnv['hasAppiumDependency']} */ ( + hasAppiumDependency: /** @type {MockAppiumSupportEnv['hasAppiumDependency']} */ ( sandbox.stub().resolves(false) ), - readPackageInDir: - /** @type {MockAppiumSupportEnv['readPackageInDir']} */ ( - sandbox - .stub() - .callsFake(async () => MockAppiumSupport.env.__pkg) + readPackageInDir: /** @type {MockAppiumSupportEnv['readPackageInDir']} */ ( + sandbox.stub().callsFake(async () => MockAppiumSupport.env.__pkg) ), __pkg: { name: 'mock-package', @@ -62,11 +50,7 @@ export function initMocks (sandbox = createSandbox()) { getLogger: /** @type {MockAppiumSupportLogger['getLogger']} */ ( sandbox .stub() - .returns( - sandbox.stub( - new global.console.Console(process.stdout, process.stderr), - ), - ) + .returns(sandbox.stub(new global.console.Console(process.stdout, process.stderr))) ), }, }; diff --git a/packages/appium/test/unit/extension/plugin-config.spec.js b/packages/appium/test/unit/extension/plugin-config.spec.js index 99cef8eea..1a1d350ca 100644 --- a/packages/appium/test/unit/extension/plugin-config.spec.js +++ b/packages/appium/test/unit/extension/plugin-config.spec.js @@ -250,8 +250,7 @@ describe('PluginConfig', function () { }); it('should return an empty array', function () { - expect(pluginConfig.getSchemaProblems(externalManifest, 'foo')).to - .be.empty; + expect(pluginConfig.getSchemaProblems(externalManifest, 'foo')).to.be.empty; }); }); @@ -309,9 +308,10 @@ describe('PluginConfig', function () { it('should throw', function () { // @ts-expect-error delete extData.schema; - expect(() => - pluginConfig.readExtensionSchema(extName, extData) - ).to.throw(TypeError, /why is this function being called/i); + expect(() => pluginConfig.readExtensionSchema(extName, extData)).to.throw( + TypeError, + /why is this function being called/i + ); }); }); @@ -319,9 +319,7 @@ describe('PluginConfig', function () { describe('when the schema is identical (presumably the same extension)', function () { it('should not throw', function () { pluginConfig.readExtensionSchema(extName, extData); - expect(() => - pluginConfig.readExtensionSchema(extName, extData) - ).not.to.throw(); + expect(() => pluginConfig.readExtensionSchema(extName, extData)).not.to.throw(); }); }); @@ -329,9 +327,9 @@ describe('PluginConfig', function () { it('should throw', function () { pluginConfig.readExtensionSchema(extName, extData); MockResolveFrom.returns(resolveFixture('driver.schema.js')); - expect(() => - pluginConfig.readExtensionSchema(extName, extData) - ).to.throw(/conflicts with an existing schema/i); + expect(() => pluginConfig.readExtensionSchema(extName, extData)).to.throw( + /conflicts with an existing schema/i + ); }); }); }); diff --git a/packages/appium/test/unit/grid-register.spec.js b/packages/appium/test/unit/grid-register.spec.js index 836ab2b5e..26bba48a6 100644 --- a/packages/appium/test/unit/grid-register.spec.js +++ b/packages/appium/test/unit/grid-register.spec.js @@ -32,10 +32,7 @@ describe('grid-register', function () { axios: sandbox.stub().resolves({data: '', status: 200}), }; - ({default: registerNode} = rewiremock.proxy( - () => require('../../lib/grid-register'), - mocks - )); + ({default: registerNode} = rewiremock.proxy(() => require('../../lib/grid-register'), mocks)); }); describe('when provided a path to a config file', function () { diff --git a/packages/appium/test/unit/logger.spec.js b/packages/appium/test/unit/logger.spec.js index 01cb1bba2..0d64dc462 100644 --- a/packages/appium/test/unit/logger.spec.js +++ b/packages/appium/test/unit/logger.spec.js @@ -1,8 +1,6 @@ - -import { init as logsinkInit, clear as logsinkClear } from '../../lib/logsink'; -import { createSandbox } from 'sinon'; -import { logger } from '@appium/support'; - +import {init as logsinkInit, clear as logsinkClear} from '../../lib/logsink'; +import {createSandbox} from 'sinon'; +import {logger} from '@appium/support'; // temporarily turn on logging to stdio, so we can catch and query const forceLogs = process.env._FORCE_LOGS; @@ -30,7 +28,7 @@ describe('logging', function () { const warnMsg = 'some warning'; const debugMsg = 'some debug'; - function doLogging () { + function doLogging() { log.error(errorMsg); log.warn(warnMsg); log.debug(debugMsg); diff --git a/packages/appium/test/unit/parser.spec.js b/packages/appium/test/unit/parser.spec.js index c8a8e87a3..ca4bbcb80 100644 --- a/packages/appium/test/unit/parser.spec.js +++ b/packages/appium/test/unit/parser.spec.js @@ -1,9 +1,9 @@ -import { DRIVER_TYPE, PLUGIN_TYPE } from '../../lib/constants'; -import { getParser } from '../../lib/cli/parser'; -import { INSTALL_TYPES } from '../../lib/extension/extension-config'; +import {DRIVER_TYPE, PLUGIN_TYPE} from '../../lib/constants'; +import {getParser} from '../../lib/cli/parser'; +import {INSTALL_TYPES} from '../../lib/extension/extension-config'; import * as schema from '../../lib/schema/schema'; -import { readConfigFile } from '../../lib/config-file'; -import { resolveFixture } from '../helpers'; +import {readConfigFile} from '../../lib/config-file'; +import {resolveFixture} from '../helpers'; // these paths should not make assumptions about the current working directory const ALLOW_FIXTURE = resolveFixture('allow-feat.txt'); @@ -53,23 +53,33 @@ describe('parser', function () { // TODO: figure out how best to suppress color in error message describe('invalid arguments', function () { it('should throw an error with unknown argument', function () { - (() => {p.parseArgs(['--apple']);}).should.throw(/unrecognized arguments: --apple/i); + (() => { + p.parseArgs(['--apple']); + }).should.throw(/unrecognized arguments: --apple/i); }); it('should throw an error for an invalid value ("hostname")', function () { - (() => {p.parseArgs(['--address', '-42']);}).should.throw(/must match format "hostname"/i); + (() => { + p.parseArgs(['--address', '-42']); + }).should.throw(/must match format "hostname"/i); }); it('should throw an error for an invalid value ("uri")', function () { - (() => {p.parseArgs(['--webhook', 'blub']);}).should.throw(/must match format "uri"/i); + (() => { + p.parseArgs(['--webhook', 'blub']); + }).should.throw(/must match format "uri"/i); }); it('should throw an error for an invalid value (using "enum")', function () { - (() => {p.parseArgs(['--log-level', '-42']);}).should.throw(/must be equal to one of the allowed values/i); + (() => { + p.parseArgs(['--log-level', '-42']); + }).should.throw(/must be equal to one of the allowed values/i); }); it('should throw an error for incorrectly formatted arg (matching "dest")', function () { - (() => {p.parseArgs(['--loglevel', '-42']);}).should.throw(/unrecognized arguments: --loglevel/i); + (() => { + p.parseArgs(['--loglevel', '-42']); + }).should.throw(/unrecognized arguments: --loglevel/i); }); }); @@ -86,10 +96,18 @@ describe('parser', function () { }); it('should throw an error with invalid arg to default capabilities', function () { - (() => {p.parseArgs(['-dc', '42']);}).should.throw(); - (() => {p.parseArgs(['-dc', 'false']);}).should.throw(); - (() => {p.parseArgs(['-dc', 'null']);}).should.throw(); - (() => {p.parseArgs(['-dc', 'does/not/exist.json']);}).should.throw(); + (() => { + p.parseArgs(['-dc', '42']); + }).should.throw(); + (() => { + p.parseArgs(['-dc', 'false']); + }).should.throw(); + (() => { + p.parseArgs(['-dc', 'null']); + }).should.throw(); + (() => { + p.parseArgs(['-dc', 'does/not/exist.json']); + }).should.throw(); }); it('should parse --allow-insecure correctly', function () { @@ -110,7 +128,10 @@ describe('parser', function () { it('should parse --allow-insecure & --deny-insecure from files', function () { const parsed = p.parseArgs([ - '--allow-insecure', ALLOW_FIXTURE, '--deny-insecure', DENY_FIXTURE + '--allow-insecure', + ALLOW_FIXTURE, + '--deny-insecure', + DENY_FIXTURE, ]); parsed.allowInsecure.should.eql(['feature1', 'feature2', 'feature3']); parsed.denyInsecure.should.eql(['nofeature1', 'nofeature2', 'nofeature3']); @@ -120,7 +141,6 @@ describe('parser', function () { p.parseArgs(['--use-drivers', 'fake']).useDrivers.should.eql(['fake']); }); - it('should allow multiple --use-drivers', function () { p.parseArgs(['--use-drivers', 'fake,phony']).useDrivers.should.eql(['fake', 'phony']); }); @@ -136,7 +156,11 @@ describe('parser', function () { // we have to require() here because babel will not compile stuff in node_modules // (even if it's in the monorepo; there may be a way around this) // anyway, if we do that, we need to use the `default` prop. - schema.registerSchema(DRIVER_TYPE, 'fake', require('@appium/fake-driver/build/lib/fake-driver-schema').default); + schema.registerSchema( + DRIVER_TYPE, + 'fake', + require('@appium/fake-driver/build/lib/fake-driver-schema').default + ); schema.finalizeSchema(); p = getParser(true); }); @@ -147,12 +171,14 @@ describe('parser', function () { // the command-line flags are derived also from the schema. // the result should be that the parsed args should match the config file. const {config} = await readConfigFile(resolveFixture('config', 'driver-fake.config.json')); - const fakeDriverArgs = {fake: {sillyWebServerPort: 1234, sillyWebServerHost: 'hey'}}; + const fakeDriverArgs = { + fake: {sillyWebServerPort: 1234, sillyWebServerHost: 'hey'}, + }; const args = p.parseArgs([ '--driver-fake-silly-web-server-port', fakeDriverArgs.fake.sillyWebServerPort, '--driver-fake-silly-web-server-host', - fakeDriverArgs.fake.sillyWebServerHost + fakeDriverArgs.fake.sillyWebServerHost, ]); args.driver.fake.should.eql(config.driver.fake); @@ -165,30 +191,36 @@ describe('parser', function () { it('should nicely handle extensions w/ dashes in them', function () { schema.resetSchema(); - schema.registerSchema(PLUGIN_TYPE, 'crypto-fiend', {type: 'object', properties: {elite: {type: 'boolean'}}}); + schema.registerSchema(PLUGIN_TYPE, 'crypto-fiend', { + type: 'object', + properties: {elite: {type: 'boolean'}}, + }); schema.finalizeSchema(); p = getParser(true); - const args = p.parseArgs([ - '--plugin-crypto-fiend-elite' - ]); + const args = p.parseArgs(['--plugin-crypto-fiend-elite']); args.should.have.nested.property('plugin.crypto-fiend.elite', true); }); describe('when user supplies invalid args', function () { it('should error out', function () { - (() => p.parseArgs(['--driver-fake-silly-web-server-port', 'foo'])).should.throw(/must be integer/i); + (() => p.parseArgs(['--driver-fake-silly-web-server-port', 'foo'])).should.throw( + /must be integer/i + ); }); }); it('should not support --driver-args', function () { - (() => p.parseArgs(['--driver-args', '/some/file.json'])).should.throw(/unrecognized arguments/i); + (() => p.parseArgs(['--driver-args', '/some/file.json'])).should.throw( + /unrecognized arguments/i + ); }); it('should not support --plugin-args', function () { - (() => p.parseArgs(['--plugin-args', '/some/file.json'])).should.throw(/unrecognized arguments/i); + (() => p.parseArgs(['--plugin-args', '/some/file.json'])).should.throw( + /unrecognized arguments/i + ); }); - }); }); diff --git a/packages/appium/test/unit/schema/arg-spec.spec.js b/packages/appium/test/unit/schema/arg-spec.spec.js index 0995fd808..fb8973b58 100644 --- a/packages/appium/test/unit/schema/arg-spec.spec.js +++ b/packages/appium/test/unit/schema/arg-spec.spec.js @@ -1,7 +1,7 @@ // @ts-check -import { DRIVER_TYPE } from '../../../lib/constants'; -import { ArgSpec } from '../../../lib/schema/arg-spec'; +import {DRIVER_TYPE} from '../../../lib/constants'; +import {ArgSpec} from '../../../lib/schema/arg-spec'; const {expect} = chai; @@ -17,7 +17,7 @@ describe('ArgSpec', function () { describe('when provided no extension information', function () { it('should return a schema ID for a specific argument', function () { expect(ArgSpec.toSchemaRef('foo')).to.equal( - 'appium.json#/properties/server/properties/foo', + 'appium.json#/properties/server/properties/foo' ); }); }); @@ -25,7 +25,7 @@ describe('ArgSpec', function () { describe('when provided extension information', function () { it('should return a schema ID for a specific argument within an extension schema', function () { expect(ArgSpec.toSchemaRef('bar', DRIVER_TYPE, 'stuff')).to.equal( - 'driver-stuff.json#/properties/bar', + 'driver-stuff.json#/properties/bar' ); }); }); @@ -34,17 +34,13 @@ describe('ArgSpec', function () { describe('toSchemaBaseRef()', function () { describe('when provided no extension information', function () { it('should return the base schema ID', function () { - expect(ArgSpec.toSchemaBaseRef()).to.equal( - 'appium.json', - ); + expect(ArgSpec.toSchemaBaseRef()).to.equal('appium.json'); }); }); describe('when provided extension information', function () { it('should return a schema ID for an extension', function () { - expect(ArgSpec.toSchemaBaseRef(DRIVER_TYPE, 'stuff')).to.equal( - 'driver-stuff.json', - ); + expect(ArgSpec.toSchemaBaseRef(DRIVER_TYPE, 'stuff')).to.equal('driver-stuff.json'); }); }); }); @@ -59,7 +55,7 @@ describe('ArgSpec', function () { describe('when provided extension information', function () { it('should return an extension-specific arg name', function () { expect(ArgSpec.toArg('no-oats', DRIVER_TYPE, 'bad-donkey')).to.equal( - 'driver-bad-donkey-no-oats', + 'driver-bad-donkey-no-oats' ); }); }); @@ -68,15 +64,15 @@ describe('ArgSpec', function () { describe('extensionInfoFromRootSchemaId()', function () { describe('when provided the base schema ID', function () { it('should return an empty object', function () { - expect(ArgSpec.extensionInfoFromRootSchemaId('appium.json')).to.be - .empty; + expect(ArgSpec.extensionInfoFromRootSchemaId('appium.json')).to.be.empty; }); }); describe('when provided the schema ID of an extension schema', function () { - expect( - ArgSpec.extensionInfoFromRootSchemaId('driver-stuff.json'), - ).to.eql({extType: DRIVER_TYPE, normalizedExtName: 'stuff'}); + expect(ArgSpec.extensionInfoFromRootSchemaId('driver-stuff.json')).to.eql({ + extType: DRIVER_TYPE, + normalizedExtName: 'stuff', + }); }); }); }); diff --git a/packages/appium/test/unit/schema/cli-args.spec.js b/packages/appium/test/unit/schema/cli-args.spec.js index 44e72d07c..897c9cc0b 100644 --- a/packages/appium/test/unit/schema/cli-args.spec.js +++ b/packages/appium/test/unit/schema/cli-args.spec.js @@ -1,10 +1,10 @@ // @ts-check import _ from 'lodash'; -import { PLUGIN_TYPE } from '../../../lib/constants'; -import { finalizeSchema, registerSchema, resetSchema } from '../../../lib/schema'; -import { toParserArgs } from '../../../lib/schema/cli-args'; -import { transformers } from '../../../lib/schema/cli-transformers'; +import {PLUGIN_TYPE} from '../../../lib/constants'; +import {finalizeSchema, registerSchema, resetSchema} from '../../../lib/schema'; +import {toParserArgs} from '../../../lib/schema/cli-args'; +import {transformers} from '../../../lib/schema/cli-transformers'; const {expect} = chai; @@ -15,7 +15,7 @@ describe('cli-args', function () { * @param {*} opts * @returns */ - function getArgs (opts = {}) { + function getArgs(opts = {}) { let {extName, extType, schema} = opts; if (schema && extName && extType) { registerSchema(extType, extName, schema); @@ -37,15 +37,15 @@ describe('cli-args', function () { describe('boolean', function () { beforeEach(function () { - const schema = {properties: {foo: {type: 'boolean'}}, type: 'object'}; + const schema = { + properties: {foo: {type: 'boolean'}}, + type: 'object', + }; result = getArgs({schema, extName, extType}); }); it('should return options containing `action` prop of `store_const` and no `type`', function () { - expect(result['--plugin-blob-foo']).to.have.property( - 'action', - 'store_const', - ); + expect(result['--plugin-blob-foo']).to.have.property('action', 'store_const'); }); it('should not contain a `metavar` property', function () { @@ -55,22 +55,19 @@ describe('cli-args', function () { describe('object', function () { beforeEach(function () { - const schema = {properties: {foo: {type: 'object'}}, type: 'object'}; + const schema = { + properties: {foo: {type: 'object'}}, + type: 'object', + }; result = getArgs({schema, extName, extType}); }); it('should use the `json` transformer', function () { - expect(result['--plugin-blob-foo']).to.have.property( - 'type', - transformers.json, - ); + expect(result['--plugin-blob-foo']).to.have.property('type', transformers.json); }); it('should contain a SCREAMING_SNAKE_CASE `metavar` prop', function () { - expect(result['--plugin-blob-foo']).to.have.property( - 'metavar', - 'FOO', - ); + expect(result['--plugin-blob-foo']).to.have.property('metavar', 'FOO'); }); }); @@ -81,23 +78,20 @@ describe('cli-args', function () { }); it('should use the `csv` transformer', function () { - expect(result['--plugin-blob-foo']).to.have.property( - 'type', - transformers.csv, - ); + expect(result['--plugin-blob-foo']).to.have.property('type', transformers.csv); }); it('should contain a SCREAMING_SNAKE_CASE `metavar` prop', function () { - expect(result['--plugin-blob-foo']).to.have.property( - 'metavar', - 'FOO', - ); + expect(result['--plugin-blob-foo']).to.have.property('metavar', 'FOO'); }); }); describe('number', function () { beforeEach(function () { - const schema = {properties: {foo: {type: 'number'}}, type: 'object'}; + const schema = { + properties: {foo: {type: 'number'}}, + type: 'object', + }; result = getArgs({schema, extName, extType}); }); @@ -106,16 +100,16 @@ describe('cli-args', function () { }); it('should contain a SCREAMING_SNAKE_CASE `metavar` prop', function () { - expect(result['--plugin-blob-foo']).to.have.property( - 'metavar', - 'FOO', - ); + expect(result['--plugin-blob-foo']).to.have.property('metavar', 'FOO'); }); }); describe('integer', function () { beforeEach(function () { - const schema = {properties: {foo: {type: 'integer'}}, type: 'object'}; + const schema = { + properties: {foo: {type: 'integer'}}, + type: 'object', + }; result = getArgs({schema, extName, extType}); }); @@ -124,10 +118,7 @@ describe('cli-args', function () { }); it('should contain a SCREAMING_SNAKE_CASE `metavar` prop', function () { - expect(result['--plugin-blob-foo']).to.have.property( - 'metavar', - 'FOO', - ); + expect(result['--plugin-blob-foo']).to.have.property('metavar', 'FOO'); }); }); @@ -145,10 +136,7 @@ describe('cli-args', function () { }); it('should contain a SCREAMING_SNAKE_CASE `metavar` prop', function () { - expect(result['--plugin-blob-foo']).to.have.property( - 'metavar', - 'FOO', - ); + expect(result['--plugin-blob-foo']).to.have.property('metavar', 'FOO'); }); }); @@ -157,7 +145,7 @@ describe('cli-args', function () { const schema = {properties: {foo: {type: 'null'}}, type: 'object'}; expect(() => getArgs({extType, extName, schema})).to.throw( TypeError, - /unknown or disallowed/, + /unknown or disallowed/ ); }); }); @@ -168,10 +156,7 @@ describe('cli-args', function () { properties: {foo: {type: 'donkey'}}, type: 'object', }; - expect(() => getArgs({extType, extName, schema})).to.throw( - Error, - /schema is invalid/, - ); + expect(() => getArgs({extType, extName, schema})).to.throw(Error, /schema is invalid/); }); }); }); @@ -187,9 +172,7 @@ describe('cli-args', function () { type: 'object', }; result = getArgs({schema, extName, extType}); - expect(result).to.have.property( - '--plugin-blob-foo,--plugin-blob-fooooo,--plugin-blob-F', - ); + expect(result).to.have.property('--plugin-blob-foo,--plugin-blob-fooooo,--plugin-blob-F'); }); }); @@ -232,9 +215,7 @@ describe('cli-args', function () { type: 'object', }; result = getArgs({schema, extName, extType}); - expect(() => result['--plugin-blob-foo'].type('123')).to.throw( - /must be a valid json/i, - ); + expect(() => result['--plugin-blob-foo'].type('123')).to.throw(/must be a valid json/i); }); // this is unlikely to happen, but I want to establish the behavior as defined. @@ -254,7 +235,7 @@ describe('cli-args', function () { }; result = getArgs({schema, extName, extType}); expect(() => result['--plugin-blob-foo'].type('herp')).to.throw( - /must be a valid json/i, + /must be a valid json/i ); }); }); @@ -274,9 +255,7 @@ describe('cli-args', function () { type: 'object', }; result = getArgs({schema, extName, extType}); - expect( - result['--plugin-blob-foo'].type('{"herp": "derp"}'), - ).to.eql({herp: 'derp'}); + expect(result['--plugin-blob-foo'].type('{"herp": "derp"}')).to.eql({herp: 'derp'}); }); }); @@ -293,9 +272,9 @@ describe('cli-args', function () { type: 'object', }; result = getArgs({schema, extName, extType}); - expect(() => - result['--plugin-blob-foo'].type('{"georgy": "porgy"}'), - ).to.throw(/one of the allowed values/i); + expect(() => result['--plugin-blob-foo'].type('{"georgy": "porgy"}')).to.throw( + /one of the allowed values/i + ); }); }); }); @@ -316,12 +295,12 @@ describe('cli-args', function () { }; expect(() => getArgs({schema, extName, extType})).to.throw( TypeError, - /`enum` is only supported for `type: 'string'`/i, + /`enum` is only supported for `type: 'string'`/i ); }); it( - 'should actually throw earlier by failing schema validation, but that would mean overriding the behavior of `enum` which sounds inadvisable', + 'should actually throw earlier by failing schema validation, but that would mean overriding the behavior of `enum` which sounds inadvisable' ); }); @@ -337,10 +316,7 @@ describe('cli-args', function () { type: 'object', }; const result = getArgs({schema, extName, extType}); - expect(result['--plugin-blob-foo']).to.have.deep.property( - 'choices', - ['herp', 'derp'], - ); + expect(result['--plugin-blob-foo']).to.have.deep.property('choices', ['herp', 'derp']); }); }); }); diff --git a/packages/appium/test/unit/schema/schema.spec.js b/packages/appium/test/unit/schema/schema.spec.js index 04b85e108..e914986b9 100644 --- a/packages/appium/test/unit/schema/schema.spec.js +++ b/packages/appium/test/unit/schema/schema.spec.js @@ -144,10 +144,7 @@ describe('schema', function () { expect(() => { // @ts-expect-error registerSchema(DRIVER_TYPE, 'whoopeee', [45]); - }).to.throw( - SchemaUnsupportedSchemaError, - /must be a plain object/i - ); + }).to.throw(SchemaUnsupportedSchemaError, /must be a plain object/i); }); }); @@ -156,10 +153,7 @@ describe('schema', function () { expect(() => { // @ts-expect-error registerSchema(DRIVER_TYPE, 'whoopee', {$async: true}); - }).to.throw( - SchemaUnsupportedSchemaError, - /cannot be an async schema/i - ); + }).to.throw(SchemaUnsupportedSchemaError, /cannot be an async schema/i); }); }); @@ -178,9 +172,7 @@ describe('schema', function () { it('should not throw', function () { const schemaObject = {title: 'whoopee'}; registerSchema(DRIVER_TYPE, 'whoopee', schemaObject); - expect(() => - registerSchema(DRIVER_TYPE, 'whoopee', schemaObject) - ).not.to.throw(); + expect(() => registerSchema(DRIVER_TYPE, 'whoopee', schemaObject)).not.to.throw(); }); }); @@ -201,9 +193,7 @@ describe('schema', function () { describe('when provided a nonempty `type`, `schema` and `name`', function () { it('should register the schema', function () { const schemaObject = {title: 'whoopee'}; - expect(() => - registerSchema(DRIVER_TYPE, 'whoopee', schemaObject) - ).not.to.throw(); + expect(() => registerSchema(DRIVER_TYPE, 'whoopee', schemaObject)).not.to.throw(); }); describe('when the `name` is not unique but `type` is', function () { @@ -211,9 +201,7 @@ describe('schema', function () { const schema1 = {title: 'pro-skub'}; const schema2 = {title: 'anti-skub'}; registerSchema(DRIVER_TYPE, 'skub', schema1); - expect(() => - registerSchema(PLUGIN_TYPE, 'skub', schema2) - ).not.to.throw(); + expect(() => registerSchema(PLUGIN_TYPE, 'skub', schema2)).not.to.throw(); }); }); }); @@ -243,29 +231,21 @@ describe('schema', function () { describe('when schema ID is the base schema ID', function () { it('should return the base schema', function () { - expect(getSchema(APPIUM_CONFIG_SCHEMA_ID)).to.eql( - AppiumConfigJsonSchema - ); + expect(getSchema(APPIUM_CONFIG_SCHEMA_ID)).to.eql(AppiumConfigJsonSchema); }); }); describe('when the schema ID is a reference', function () { it('should return the schema for the reference', function () { expect( - getSchema( - `${APPIUM_CONFIG_SCHEMA_ID}#/properties/server/properties/address` - ) - ).to.exist.and.to.eql( - AppiumConfigJsonSchema.properties.server.properties.address - ); + getSchema(`${APPIUM_CONFIG_SCHEMA_ID}#/properties/server/properties/address`) + ).to.exist.and.to.eql(AppiumConfigJsonSchema.properties.server.properties.address); }); }); describe('when schema ID is invalid', function () { it('should throw', function () { - expect(() => getSchema('schema-the-clown')).to.throw( - SchemaUnknownSchemaError - ); + expect(() => getSchema('schema-the-clown')).to.throw(SchemaUnknownSchemaError); }); }); }); @@ -402,8 +382,10 @@ describe('schema', function () { it('should return a Record containing all extension schemas _and_ the base schema containing references to the extension schemas', function () { const baseSchemaWithRefs = _.cloneDeep(AppiumConfigJsonSchema); - baseSchemaWithRefs.properties.server.properties.driver.properties.stuff = - {$ref: 'driver-stuff.json', $comment: 'stuff'}; + baseSchemaWithRefs.properties.server.properties.driver.properties.stuff = { + $ref: 'driver-stuff.json', + $comment: 'stuff', + }; expect(finalizeSchema()).to.eql({ [APPIUM_CONFIG_SCHEMA_ID]: baseSchemaWithRefs, 'driver-stuff.json': DRIVER_SCHEMA_FIXTURE, @@ -442,9 +424,7 @@ describe('schema', function () { describe('when provided an invalid schema ID ref', function () { it('should throw', function () { - expect(() => validate('foo', 'bar')).to.throw( - SchemaUnknownSchemaError - ); + expect(() => validate('foo', 'bar')).to.throw(SchemaUnknownSchemaError); }); }); @@ -457,8 +437,7 @@ describe('schema', function () { describe('when provided an invalid value', function () { it('should return an array containing errors', function () { - expect(validate({address: '127.0.0.1'})).to.be.an('array').and.to - .not.be.empty; + expect(validate({address: '127.0.0.1'})).to.be.an('array').and.to.not.be.empty; }); }); }); @@ -467,10 +446,7 @@ describe('schema', function () { describe('when provided a valid value', function () { it('should return an empty array of no errors', function () { expect( - validate( - '127.0.0.1', - 'appium.json#/properties/server/properties/address' - ) + validate('127.0.0.1', 'appium.json#/properties/server/properties/address') ).to.eql([]); }); }); @@ -478,10 +454,7 @@ describe('schema', function () { describe('when provided an invalid value', function () { it('should return an array containing errors', function () { expect( - validate( - '127.0.0.1', - 'appium.json#/properties/server/properties/port' - ) + validate('127.0.0.1', 'appium.json#/properties/server/properties/port') ).to.be.an('array').and.to.not.be.empty; }); }); @@ -496,26 +469,21 @@ describe('schema', function () { describe('when provided an invalid schema ID ref', function () { it('should throw', function () { - expect(() => validate('foo', 'bar')).to.throw( - SchemaUnknownSchemaError - ); + expect(() => validate('foo', 'bar')).to.throw(SchemaUnknownSchemaError); }); }); describe('when not provided a schema ID ref', function () { describe('when provided a valid value', function () { it('should return an empty array of no errors', function () { - expect(validate({server: {driver: {stuff: {answer: 99}}}})).to.eql( - [] - ); + expect(validate({server: {driver: {stuff: {answer: 99}}}})).to.eql([]); }); }); describe('when provided an invalid value', function () { it('should return an array containing errors', function () { - expect( - validate({server: {driver: {stuff: {answer: 101}}}}) - ).to.be.an('array').and.to.not.be.empty; + expect(validate({server: {driver: {stuff: {answer: 101}}}})).to.be.an('array').and.to + .not.be.empty; }); }); }); @@ -523,17 +491,14 @@ describe('schema', function () { describe('when provided a schema ID ref', function () { describe('when provided a valid value', function () { it('should return an empty array of no errors', function () { - expect(validate(99, 'driver-stuff.json#/properties/answer')).to.eql( - [] - ); + expect(validate(99, 'driver-stuff.json#/properties/answer')).to.eql([]); }); }); describe('when provided an invalid value', function () { it('should return an array containing errors', function () { - expect( - validate(101, 'driver-stuff.json#/properties/answer') - ).to.be.an('array').and.to.not.be.empty; + expect(validate(101, 'driver-stuff.json#/properties/answer')).to.be.an('array').and.to + .not.be.empty; }); }); }); diff --git a/packages/appium/test/unit/utils.spec.js b/packages/appium/test/unit/utils.spec.js index 37cef4511..47b0fe0f3 100644 --- a/packages/appium/test/unit/utils.spec.js +++ b/packages/appium/test/unit/utils.spec.js @@ -1,11 +1,14 @@ import { - parseCapsForInnerDriver, insertAppiumPrefixes, pullSettings, - removeAppiumPrefixes, inspect + parseCapsForInnerDriver, + insertAppiumPrefixes, + pullSettings, + removeAppiumPrefixes, + inspect, } from '../../lib/utils'; -import { BASE_CAPS, W3C_CAPS } from '../helpers'; +import {BASE_CAPS, W3C_CAPS} from '../helpers'; import _ from 'lodash'; -import { stripColors } from '@colors/colors'; -import { createSandbox } from 'sinon'; +import {stripColors} from '@colors/colors'; +import {createSandbox} from 'sinon'; import logger from '../../lib/logger'; describe('utils', function () { @@ -16,14 +19,16 @@ describe('utils', function () { error.message.should.match(/W3C/); }); it('should return W3C caps unchanged if only W3C caps were provided', function () { - let {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol} = parseCapsForInnerDriver(undefined, W3C_CAPS); + let {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol} = + parseCapsForInnerDriver(undefined, W3C_CAPS); desiredCaps.should.deep.equal(BASE_CAPS); should.not.exist(processedJsonwpCapabilities); processedW3CCapabilities.should.deep.equal(W3C_CAPS); protocol.should.equal('W3C'); }); it('should return JSONWP and W3C caps if both were provided', function () { - let {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol} = parseCapsForInnerDriver(BASE_CAPS, W3C_CAPS); + let {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, protocol} = + parseCapsForInnerDriver(BASE_CAPS, W3C_CAPS); desiredCaps.should.deep.equal(BASE_CAPS); processedJsonwpCapabilities.should.deep.equal(BASE_CAPS); processedW3CCapabilities.should.deep.equal(W3C_CAPS); @@ -38,54 +43,68 @@ describe('utils', function () { foo: 'bar', baz: 'bla', }; - const { - desiredCaps, - processedJsonwpCapabilities, - processedW3CCapabilities - } = parseCapsForInnerDriver(BASE_CAPS, W3C_CAPS, {}, defaultW3CCaps); + const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities} = + parseCapsForInnerDriver(BASE_CAPS, W3C_CAPS, {}, defaultW3CCaps); desiredCaps.should.deep.equal({ ...expectedDefaultCaps, ...BASE_CAPS, }); processedJsonwpCapabilities.should.deep.equal({ ...expectedDefaultCaps, - ...BASE_CAPS + ...BASE_CAPS, }); processedW3CCapabilities.alwaysMatch.should.deep.equal({ ...insertAppiumPrefixes(expectedDefaultCaps), - ...insertAppiumPrefixes(BASE_CAPS) + ...insertAppiumPrefixes(BASE_CAPS), }); }); it('should allow valid default capabilities', function () { - const res = parseCapsForInnerDriver(null, W3C_CAPS, {}, { - 'appium:foo': 'bar2', - }); + const res = parseCapsForInnerDriver( + null, + W3C_CAPS, + {}, + { + 'appium:foo': 'bar2', + } + ); res.processedW3CCapabilities.alwaysMatch['appium:foo'].should.eql('bar2'); }); it('should not allow invalid default capabilities', function () { - const res = parseCapsForInnerDriver(null, W3C_CAPS, {}, { - foo: 'bar', 'appium:foo2': 'bar2', - }); + const res = parseCapsForInnerDriver( + null, + W3C_CAPS, + {}, + { + foo: 'bar', + 'appium:foo2': 'bar2', + } + ); res.error.should.eql({ - jsonwpCode: 61, error: 'invalid argument', w3cStatus: 400, _stacktrace: null + jsonwpCode: 61, + error: 'invalid argument', + w3cStatus: 400, + _stacktrace: null, }); }); it('should reject if W3C caps are not passing constraints', function () { - const err = parseCapsForInnerDriver(undefined, W3C_CAPS, {hello: {presence: true}}).error; + const err = parseCapsForInnerDriver(undefined, W3C_CAPS, { + hello: {presence: true}, + }).error; err.message.should.match(/'hello' can't be blank/); _.isError(err).should.be.true; - }); it('should only accept W3C caps that have passing constraints', function () { let w3cCaps = { ...W3C_CAPS, - firstMatch: [ - {foo: 'bar'}, - {'appium:hello': 'world'}, - ], + firstMatch: [{foo: 'bar'}, {'appium:hello': 'world'}], }; - parseCapsForInnerDriver(BASE_CAPS, w3cCaps, {hello: {presence: true}}).error.should.eql({ - jsonwpCode: 61, error: 'invalid argument', w3cStatus: 400, _stacktrace: null + parseCapsForInnerDriver(BASE_CAPS, w3cCaps, { + hello: {presence: true}, + }).error.should.eql({ + jsonwpCode: 61, + error: 'invalid argument', + w3cStatus: 400, + _stacktrace: null, }); }); it('should add appium prefixes to W3C caps that are not standard in W3C', function () { @@ -105,7 +124,7 @@ describe('utils', function () { 'ms:cap2': 'value2', someCap: 'someCap', }).should.eql({ - 'cap1': 'value1', + cap1: 'value1', 'ms:cap2': 'value2', someCap: 'someCap', }); @@ -213,7 +232,6 @@ describe('utils', function () { }); describe('inspect()', function () { - /** * @type {sinon.SinonSandbox} */ @@ -229,8 +247,9 @@ describe('utils', function () { it('should log the result of inspecting a value', function () { inspect({foo: 'bar'}); - stripColors(/** @type {sinon.SinonStub} */(logger.info).firstCall.firstArg) - .should.match(/\{\s*\n*foo:\s'bar'\s*\n*\}/); + stripColors(/** @type {sinon.SinonStub} */ (logger.info).firstCall.firstArg).should.match( + /\{\s*\n*foo:\s'bar'\s*\n*\}/ + ); }); }); }); diff --git a/packages/appium/types/appium-manifest.ts b/packages/appium/types/appium-manifest.ts index a81cb934e..d2d8f785c 100644 --- a/packages/appium/types/appium-manifest.ts +++ b/packages/appium/types/appium-manifest.ts @@ -1,18 +1,18 @@ -import { CommonMetadata, ExtMetadata, SchemaMetadata } from './external-manifest'; -import { ExtensionType, DriverType, PluginType } from '.'; +import {CommonMetadata, ExtMetadata, SchemaMetadata} from './external-manifest'; +import {ExtensionType, DriverType, PluginType} from '.'; export type InstallType = 'npm' | 'git' | 'local' | 'github'; export interface InternalMetadata { /** * Package name of extension - * + * * `name` from its `package.json` */ pkgName: string; /** * Version of extension - * + * * `version` from its `package.json` */ version: string; @@ -32,11 +32,7 @@ export interface InternalMetadata { */ export type ExtManifest = Omit< ExtMetadata, - ExtType extends DriverType - ? 'driverName' - : ExtType extends PluginType - ? 'pluginName' - : never + ExtType extends DriverType ? 'driverName' : ExtType extends PluginType ? 'pluginName' : never > & InternalMetadata & CommonMetadata; // XXX: ExtMetadata should be a union with CommonMetadata. why is this needed? @@ -48,16 +44,13 @@ export type WithSchemaManifest = { /** * This is just a {@linkcode ExtManifest} except it _for sure_ has a `schema` prop. */ -export type ExtManifestWithSchema = - ExtManifest & WithSchemaManifest; +export type ExtManifestWithSchema = ExtManifest & + WithSchemaManifest; /** * Generic type for an object keyed by extension name, with values of type {@linkcode ExtData} */ -export type ExtRecord = Record< - string, - ExtManifest ->; +export type ExtRecord = Record>; export type DriverRecord = ExtRecord; export type PluginRecord = ExtRecord; diff --git a/packages/appium/types/cli.ts b/packages/appium/types/cli.ts index 94b327c15..92008e907 100644 --- a/packages/appium/types/cli.ts +++ b/packages/appium/types/cli.ts @@ -17,10 +17,7 @@ export type PluginSubcommand = typeof PLUGIN_SUBCOMMAND; /** * Possible subcommands for the `appium` CLI. */ -export type CliSubcommand = - | ServerSubcommand - | DriverSubcommand - | PluginSubcommand; +export type CliSubcommand = ServerSubcommand | DriverSubcommand | PluginSubcommand; /** * Possible subcommands of {@linkcode DriverSubcommand} or @@ -127,11 +124,7 @@ export interface WithExtSubcommand { */ type CommonArgs = MoreArgs & ProgrammaticArgs & - (T extends WithServerSubcommand - ? SArgs - : T extends WithExtSubcommand - ? ExtArgs - : never); + (T extends WithServerSubcommand ? SArgs : T extends WithExtSubcommand ? ExtArgs : never); /** * Fully-parsed arguments, containing defaults, computed args, and config file values. diff --git a/packages/appium/types/extension.ts b/packages/appium/types/extension.ts index 5c845ca25..995439f8e 100644 --- a/packages/appium/types/extension.ts +++ b/packages/appium/types/extension.ts @@ -1,9 +1,8 @@ -import type { BaseDriverBase } from '@appium/base-driver/lib/basedriver/driver'; -import { Class, Driver, ExternalDriver } from '@appium/types'; -import { DriverType, ExtensionType, PluginType } from '.'; +import type {BaseDriverBase} from '@appium/base-driver/lib/basedriver/driver'; +import {Class, Driver, ExternalDriver} from '@appium/types'; +import {DriverType, ExtensionType, PluginType} from '.'; -export type DriverClass = BaseDriverBase; +export type DriverClass = BaseDriverBase; /** * Additional static props for external driver classes @@ -40,10 +39,7 @@ export interface PluginProto { /** * Don't know what this is, but it's also required. */ - onUnexpectedShutdown?: ( - driver: Driver, - cause: Error | string, - ) => Promise; + onUnexpectedShutdown?: (driver: Driver, cause: Error | string) => Promise; } /** diff --git a/packages/appium/types/external-manifest.ts b/packages/appium/types/external-manifest.ts index b6bdc7e09..24483da1f 100644 --- a/packages/appium/types/external-manifest.ts +++ b/packages/appium/types/external-manifest.ts @@ -2,9 +2,9 @@ * These types describe information about external extensions and the contents of their `package.json` files */ -import type { SchemaObject } from 'ajv'; -import type { PackageJson, SetRequired } from 'type-fest'; -import { DriverType, ExtensionType, PluginType } from './index'; +import type {SchemaObject} from 'ajv'; +import type {PackageJson, SetRequired} from 'type-fest'; +import {DriverType, ExtensionType, PluginType} from './index'; /** * This is what is allowed in the `appium.schema` prop of an extension's `package.json`. @@ -40,13 +40,12 @@ export interface PluginMetadata { * Generic type to refer to either {@linkcode DriverMetadata} or {@linkcode PluginMetadata} * Corresponds to the `appium` prop in an extension's `package.json`. */ -export type ExtMetadata = - (ExtType extends DriverType - ? DriverMetadata - : ExtType extends PluginType - ? PluginMetadata - : never) & - CommonMetadata; +export type ExtMetadata = (ExtType extends DriverType + ? DriverMetadata + : ExtType extends PluginType + ? PluginMetadata + : never) & + CommonMetadata; /** * A `package.json` containing extension metadata. diff --git a/packages/base-driver/lib/basedriver/capabilities.js b/packages/base-driver/lib/basedriver/capabilities.js index d166b42eb..23acfe0ef 100644 --- a/packages/base-driver/lib/basedriver/capabilities.js +++ b/packages/base-driver/lib/basedriver/capabilities.js @@ -1,10 +1,10 @@ // @ts-check import _ from 'lodash'; -import { validator } from './desired-caps'; -import { util } from '@appium/support'; +import {validator} from './desired-caps'; +import {util} from '@appium/support'; import log from './logger'; -import { errors } from '../protocol/errors'; +import {errors} from '../protocol/errors'; const APPIUM_VENDOR_PREFIX = 'appium:'; const APPIUM_OPTS_CAP = 'options'; @@ -17,13 +17,17 @@ const PREFIXED_APPIUM_OPTS_CAP = `${APPIUM_VENDOR_PREFIX}${APPIUM_OPTS_CAP}`; * @param {Capabilities} [secondary] * @returns {Capabilities} */ -function mergeCaps (primary = {}, secondary = {}) { +function mergeCaps(primary = {}, secondary = {}) { let result = Object.assign({}, primary); for (let [name, value] of _.toPairs(secondary)) { // Overwriting is not allowed. Primary and secondary must have different properties (w3c rule 4.4) if (!_.isUndefined(primary[name])) { - throw new errors.InvalidArgumentError(`property '${name}' should not exist on both primary (${JSON.stringify(primary)}) and secondary (${JSON.stringify(secondary)}) object`); + throw new errors.InvalidArgumentError( + `property '${name}' should not exist on both primary (${JSON.stringify( + primary + )}) and secondary (${JSON.stringify(secondary)}) object` + ); } result[name] = value; } @@ -39,8 +43,7 @@ function mergeCaps (primary = {}, secondary = {}) { * @param {ValidateCapsOpts} [opts] * @returns {Capabilities} */ -function validateCaps (caps, constraints = {}, opts = {}) { - +function validateCaps(caps, constraints = {}, opts = {}) { let {skipPresenceConstraint} = opts; if (!_.isPlainObject(caps)) { @@ -56,9 +59,9 @@ function validateCaps (caps, constraints = {}, opts = {}) { } } - let validationErrors = validator.validate(_.pickBy(caps, util.hasValue), - constraints, - {fullMessages: false}); + let validationErrors = validator.validate(_.pickBy(caps, util.hasValue), constraints, { + fullMessages: false, + }); if (validationErrors) { let message = []; @@ -84,16 +87,19 @@ const STANDARD_CAPS = [ 'proxy', 'setWindowRect', 'timeouts', - 'unhandledPromptBehavior' + 'unhandledPromptBehavior', ]; -function isStandardCap (cap) { - return !!_.find(STANDARD_CAPS, (standardCap) => standardCap.toLowerCase() === `${cap}`.toLowerCase()); +function isStandardCap(cap) { + return !!_.find( + STANDARD_CAPS, + (standardCap) => standardCap.toLowerCase() === `${cap}`.toLowerCase() + ); } // If the 'appium:' prefix was provided and it's a valid capability, strip out the prefix (see https://www.w3.org/TR/webdriver/#dfn-extension-capabilities) // (NOTE: Method is destructive and mutates contents of caps) -function stripAppiumPrefixes (caps) { +function stripAppiumPrefixes(caps) { const prefix = 'appium:'; const prefixedCaps = _.filter(_.keys(caps), (cap) => `${cap}`.startsWith(prefix)); const badPrefixedCaps = []; @@ -108,8 +114,10 @@ function stripAppiumPrefixes (caps) { if (_.isNil(caps[strippedCapName])) { caps[strippedCapName] = caps[prefixedCap]; } else { - log.warn(`Ignoring capability '${prefixedCap}=${caps[prefixedCap]}' and ` + - `using capability '${strippedCapName}=${caps[strippedCapName]}'`); + log.warn( + `Ignoring capability '${prefixedCap}=${caps[prefixedCap]}' and ` + + `using capability '${strippedCapName}=${caps[strippedCapName]}'` + ); } } else { caps[strippedCapName] = caps[prefixedCap]; @@ -121,7 +129,11 @@ function stripAppiumPrefixes (caps) { // If we found standard caps that were incorrectly prefixed, throw an exception (e.g.: don't accept 'appium:platformName', only accept just 'platformName') if (badPrefixedCaps.length > 0) { - log.warn(`The capabilities ${JSON.stringify(badPrefixedCaps)} are standard capabilities and do not require "appium:" prefix`); + log.warn( + `The capabilities ${JSON.stringify( + badPrefixedCaps + )} are standard capabilities and do not require "appium:" prefix` + ); } } @@ -129,12 +141,15 @@ function stripAppiumPrefixes (caps) { * Get an array of all the unprefixed caps that are being used in 'alwaysMatch' and all of the 'firstMatch' object * @param {Object} caps A capabilities object */ -function findNonPrefixedCaps ({alwaysMatch = {}, firstMatch = []}) { +function findNonPrefixedCaps({alwaysMatch = {}, firstMatch = []}) { return _.chain([alwaysMatch, ...firstMatch]) - .reduce((unprefixedCaps, caps) => [ - ...unprefixedCaps, - ...Object.keys(caps).filter((cap) => !cap.includes(':') && !isStandardCap(cap)), - ], []) + .reduce( + (unprefixedCaps, caps) => [ + ...unprefixedCaps, + ...Object.keys(caps).filter((cap) => !cap.includes(':') && !isStandardCap(cap)), + ], + [] + ) .uniq() .value(); } @@ -147,10 +162,12 @@ function findNonPrefixedCaps ({alwaysMatch = {}, firstMatch = []}) { * @param {boolean} [shouldValidateCaps] * @returns */ -function parseCaps (caps, constraints = {}, shouldValidateCaps = true) { +function parseCaps(caps, constraints = {}, shouldValidateCaps = true) { // If capabilities request is not an object, return error (#1.1) if (!_.isPlainObject(caps)) { - throw new errors.InvalidArgumentError('The capabilities argument was not valid for the following reason(s): "capabilities" must be a JSON object.'); + throw new errors.InvalidArgumentError( + 'The capabilities argument was not valid for the following reason(s): "capabilities" must be a JSON object.' + ); } // Let 'requiredCaps' be property named 'alwaysMatch' from capabilities request (#2) @@ -162,21 +179,27 @@ function parseCaps (caps, constraints = {}, shouldValidateCaps = true) { // Reject 'firstMatch' argument if it's not an array (#3.2) if (!_.isArray(allFirstMatchCaps)) { - throw new errors.InvalidArgumentError('The capabilities.firstMatch argument was not valid for the following reason(s): "capabilities.firstMatch" must be a JSON array or undefined'); + throw new errors.InvalidArgumentError( + 'The capabilities.firstMatch argument was not valid for the following reason(s): "capabilities.firstMatch" must be a JSON array or undefined' + ); } // If an empty array as provided, we'll be forgiving and make it an array of one empty object // In the future, reject 'firstMatch' argument if its array did not have one or more entries (#3.2) if (allFirstMatchCaps.length === 0) { - log.warn(`The firstMatch array in the given capabilities has no entries. Adding an empty entry fo rnow, ` + - `but it will require one or more entries as W3C spec.`); + log.warn( + `The firstMatch array in the given capabilities has no entries. Adding an empty entry fo rnow, ` + + `but it will require one or more entries as W3C spec.` + ); allFirstMatchCaps.push({}); } // Check for non-prefixed, non-standard capabilities and log warnings if they are found let nonPrefixedCaps = findNonPrefixedCaps(caps); if (!_.isEmpty(nonPrefixedCaps)) { - throw new errors.InvalidArgumentError(`All non-standard capabilities should have a vendor prefix. The following capabilities did not have one: ${nonPrefixedCaps}`); + throw new errors.InvalidArgumentError( + `All non-standard capabilities should have a vendor prefix. The following capabilities did not have one: ${nonPrefixedCaps}` + ); } // Strip out the 'appium:' prefix from all @@ -187,10 +210,11 @@ function parseCaps (caps, constraints = {}, shouldValidateCaps = true) { // Validate the requiredCaps. But don't validate 'presence' because if that constraint fails on 'alwaysMatch' it could still pass on one of the 'firstMatch' keys if (shouldValidateCaps) { - requiredCaps = validateCaps(requiredCaps, constraints, {skipPresenceConstraint: true}); + requiredCaps = validateCaps(requiredCaps, constraints, { + skipPresenceConstraint: true, + }); } - // Remove the 'presence' constraint for any keys that are already present in 'requiredCaps' // since we know that this constraint has already passed let filteredConstraints = {...constraints}; @@ -204,14 +228,18 @@ function parseCaps (caps, constraints = {}, shouldValidateCaps = true) { // Validate all of the first match capabilities and return an array with only the valid caps (see spec #5) let validationErrors = []; /** @type {Capabilities[]} */ - let validatedFirstMatchCaps = _.compact(allFirstMatchCaps.map((firstMatchCaps) => { - try { - // Validate firstMatch caps - return shouldValidateCaps ? validateCaps(firstMatchCaps, filteredConstraints) : firstMatchCaps; - } catch (e) { - validationErrors.push(e.message); - } - })); + let validatedFirstMatchCaps = _.compact( + allFirstMatchCaps.map((firstMatchCaps) => { + try { + // Validate firstMatch caps + return shouldValidateCaps + ? validateCaps(firstMatchCaps, filteredConstraints) + : firstMatchCaps; + } catch (e) { + validationErrors.push(e.message); + } + }) + ); // Try to merge requiredCaps with first match capabilities, break once it finds its first match (see spec #6) let matchedCaps = null; @@ -228,7 +256,13 @@ function parseCaps (caps, constraints = {}, shouldValidateCaps = true) { } // Returns variables for testing purposes - return {requiredCaps, allFirstMatchCaps, validatedFirstMatchCaps, matchedCaps, validationErrors}; + return { + requiredCaps, + allFirstMatchCaps, + validatedFirstMatchCaps, + matchedCaps, + validationErrors, + }; } // Calls parseCaps and just returns the matchedCaps variable @@ -239,14 +273,18 @@ function parseCaps (caps, constraints = {}, shouldValidateCaps = true) { * @param {boolean} [shouldValidateCaps] * @returns {Capabilities} */ -function processCapabilities (w3cCaps, constraints = {}, shouldValidateCaps = true) { +function processCapabilities(w3cCaps, constraints = {}, shouldValidateCaps = true) { const {matchedCaps, validationErrors} = parseCaps(w3cCaps, constraints, shouldValidateCaps); // If we found an error throw an exception if (!util.hasValue(matchedCaps)) { if (_.isArray(w3cCaps.firstMatch) && w3cCaps.firstMatch.length > 1) { // If there was more than one 'firstMatch' cap, indicate that we couldn't find a matching capabilities set and show all the errors - throw new errors.InvalidArgumentError(`Could not find matching capabilities from ${JSON.stringify(w3cCaps)}:\n ${validationErrors.join('\n')}`); + throw new errors.InvalidArgumentError( + `Could not find matching capabilities from ${JSON.stringify( + w3cCaps + )}:\n ${validationErrors.join('\n')}` + ); } else { // Otherwise, just show the singular error message throw new errors.InvalidArgumentError(validationErrors[0]); @@ -267,7 +305,7 @@ function processCapabilities (w3cCaps, constraints = {}, shouldValidateCaps = tr * @param {object} originalCaps - the capabilities to analyze and promote from 'options' * @return {object!} - the capabilities with 'options' promoted if necessary */ -function promoteAppiumOptions (originalCaps) { +function promoteAppiumOptions(originalCaps) { const appiumOptions = originalCaps[APPIUM_OPTS_CAP]; if (!appiumOptions) { return originalCaps; @@ -284,8 +322,10 @@ function promoteAppiumOptions (originalCaps) { // warn if we are going to overwrite any keys on the base caps object const overwrittenKeys = _.intersection(Object.keys(caps), Object.keys(appiumOptions)); if (overwrittenKeys.length > 0) { - log.warn(`Found capabilities inside ${PREFIXED_APPIUM_OPTS_CAP} that will overwrite ` + - `capabilities at the top level: ${JSON.stringify(overwrittenKeys)}`); + log.warn( + `Found capabilities inside ${PREFIXED_APPIUM_OPTS_CAP} that will overwrite ` + + `capabilities at the top level: ${JSON.stringify(overwrittenKeys)}` + ); } // now just apply them to the main caps object @@ -296,10 +336,18 @@ function promoteAppiumOptions (originalCaps) { return caps; } - export { - parseCaps, processCapabilities, validateCaps, mergeCaps, APPIUM_VENDOR_PREFIX, APPIUM_OPTS_CAP, - findNonPrefixedCaps, isStandardCap, stripAppiumPrefixes, promoteAppiumOptions, PREFIXED_APPIUM_OPTS_CAP, + parseCaps, + processCapabilities, + validateCaps, + mergeCaps, + APPIUM_VENDOR_PREFIX, + APPIUM_OPTS_CAP, + findNonPrefixedCaps, + isStandardCap, + stripAppiumPrefixes, + promoteAppiumOptions, + PREFIXED_APPIUM_OPTS_CAP, }; /** diff --git a/packages/base-driver/lib/basedriver/commands/event.js b/packages/base-driver/lib/basedriver/commands/event.js index 0f488dce4..d871a3ccb 100644 --- a/packages/base-driver/lib/basedriver/commands/event.js +++ b/packages/base-driver/lib/basedriver/commands/event.js @@ -6,7 +6,7 @@ import _ from 'lodash'; * @param {TimeoutBase} Base * @returns {EventBase} */ -export function EventMixin (Base) { +export function EventMixin(Base) { /** * @implements {IEventCommands} */ @@ -18,7 +18,7 @@ export function EventMixin (Base) { * separation * @param {string} event - the event name */ - async logCustomEvent (vendor, event) { + async logCustomEvent(vendor, event) { this.logEvent(`${vendor}:${event}`); } @@ -28,7 +28,7 @@ export function EventMixin (Base) { * It returns all events if the type is not provided or empty string/array. * @returns {Promise>} - the event history log object */ - async getLogEvents (type) { + async getLogEvents(type) { if (_.isEmpty(type)) { return this.eventHistory; } @@ -43,7 +43,7 @@ export function EventMixin (Base) { } return acc; }, - {}, + {} ); } } diff --git a/packages/base-driver/lib/basedriver/commands/find.js b/packages/base-driver/lib/basedriver/commands/find.js index 336f0a038..18fd74af3 100644 --- a/packages/base-driver/lib/basedriver/commands/find.js +++ b/packages/base-driver/lib/basedriver/commands/find.js @@ -8,7 +8,7 @@ import {errors} from '../../protocol'; * @param {EventBase} Base * @returns {FindBase} */ -export function FindMixin (Base) { +export function FindMixin(Base) { /** * @implements {IFindCommands} */ @@ -17,7 +17,7 @@ export function FindMixin (Base) { * * @returns {Promise} */ - async findElement (strategy, selector) { + async findElement(strategy, selector) { return await this.findElOrElsWithProcessing(strategy, selector, false); } @@ -25,7 +25,7 @@ export function FindMixin (Base) { * * @returns {Promise} */ - async findElements (strategy, selector) { + async findElements(strategy, selector) { return await this.findElOrElsWithProcessing(strategy, selector, true); } @@ -33,26 +33,16 @@ export function FindMixin (Base) { * * @returns {Promise} */ - async findElementFromElement (strategy, selector, elementId) { - return await this.findElOrElsWithProcessing( - strategy, - selector, - false, - elementId, - ); + async findElementFromElement(strategy, selector, elementId) { + return await this.findElOrElsWithProcessing(strategy, selector, false, elementId); } /** * * @returns {Promise} */ - async findElementsFromElement (strategy, selector, elementId) { - return await this.findElOrElsWithProcessing( - strategy, - selector, - true, - elementId, - ); + async findElementsFromElement(strategy, selector, elementId) { + return await this.findElOrElsWithProcessing(strategy, selector, true, elementId); } // Override the following function for your own driver, and the rest is taken // care of! @@ -66,14 +56,14 @@ export function FindMixin (Base) { * @param {string} [context] * @returns {Promise} */ - async findElOrEls (strategy, selector, mult, context) { + async findElOrEls(strategy, selector, mult, context) { throw new errors.NotImplementedError('Not implemented yet for find.'); } /** * @returns {Promise} */ - async getPageSource () { + async getPageSource() { throw new errors.NotImplementedError('Not implemented yet for find.'); } /** @@ -84,19 +74,15 @@ export function FindMixin (Base) { * @param {string} [context] * @returns {Promise} */ - async findElOrElsWithProcessing (strategy, selector, mult, context) { + async findElOrElsWithProcessing(strategy, selector, mult, context) { this.validateLocatorStrategy(strategy); try { return await this.findElOrEls(strategy, selector, mult, context); } catch (err) { if (this.opts.printPageSourceOnFindFailure) { const src = await this.getPageSource(); - this.log.debug( - `Error finding element${mult ? 's' : ''}: ${err.message}`, - ); - this.log.debug( - `Page source requested through 'printPageSourceOnFindFailure':`, - ); + this.log.debug(`Error finding element${mult ? 's' : ''}: ${err.message}`); + this.log.debug(`Page source requested through 'printPageSourceOnFindFailure':`); this.log.debug(src); } // still want the error to occur diff --git a/packages/base-driver/lib/basedriver/commands/index.js b/packages/base-driver/lib/basedriver/commands/index.js index 39c874928..659d5ffe3 100644 --- a/packages/base-driver/lib/basedriver/commands/index.js +++ b/packages/base-driver/lib/basedriver/commands/index.js @@ -1,18 +1,18 @@ // @ts-check -import { EventMixin } from './event'; -import { FindMixin } from './find'; -import { LogMixin } from './log'; -import { SessionMixin } from './session'; -import { SettingsMixin } from './settings'; -import { TimeoutMixin } from './timeout'; +import {EventMixin} from './event'; +import {FindMixin} from './find'; +import {LogMixin} from './log'; +import {SessionMixin} from './session'; +import {SettingsMixin} from './settings'; +import {TimeoutMixin} from './timeout'; /** * Applies all the mixins to the `BaseDriverBase` class. * Returns a `BaseDriver` class. * @param {BaseDriverBase} Base */ -export function createBaseDriverClass (Base) { +export function createBaseDriverClass(Base) { const WithTimeoutCommands = TimeoutMixin(Base); const WithEventCommands = EventMixin(WithTimeoutCommands); const WithFindCommands = FindMixin(WithEventCommands); diff --git a/packages/base-driver/lib/basedriver/commands/log.js b/packages/base-driver/lib/basedriver/commands/log.js index cc8f4d5d5..86a19101b 100644 --- a/packages/base-driver/lib/basedriver/commands/log.js +++ b/packages/base-driver/lib/basedriver/commands/log.js @@ -8,19 +8,18 @@ import _ from 'lodash'; * @param {FindBase} Base * @returns {LogBase} */ -export function LogMixin (Base) { +export function LogMixin(Base) { /** * @implements {ILogCommands} */ class LogCommands extends Base { - - constructor (...args) { + constructor(...args) { super(...args); /** @type {Record>} */ this.supportedLogTypes = this.supportedLogTypes ?? {}; } - async getLogTypes () { + async getLogTypes() { this.log.debug('Retrieving supported log types'); return _.keys(this.supportedLogTypes); } @@ -29,14 +28,14 @@ export function LogMixin (Base) { * @this {Driver} * @param {string} logType */ - async getLog (logType) { + async getLog(logType) { this.log.debug(`Retrieving '${logType}' logs`); if (!(await this.getLogTypes()).includes(logType)) { const logsTypesWithDescriptions = _.mapValues(this.supportedLogTypes, 'description'); throw new Error( `Unsupported log type '${logType}'. ` + - `Supported types: ${JSON.stringify(logsTypesWithDescriptions)}`, + `Supported types: ${JSON.stringify(logsTypesWithDescriptions)}` ); } @@ -46,7 +45,6 @@ export function LogMixin (Base) { return LogCommands; } - /** * @typedef {import('@appium/types').LogCommands} ILogCommands * @typedef {import('@appium/types').Driver} Driver diff --git a/packages/base-driver/lib/basedriver/commands/session.js b/packages/base-driver/lib/basedriver/commands/session.js index 740114082..e9b20d199 100644 --- a/packages/base-driver/lib/basedriver/commands/session.js +++ b/packages/base-driver/lib/basedriver/commands/session.js @@ -7,7 +7,7 @@ import _ from 'lodash'; * @param {SettingsBase} Base * @returns {SessionBase} */ -export function SessionMixin (Base) { +export function SessionMixin(Base) { /** * @implements {ISessionCommands} */ @@ -15,7 +15,7 @@ export function SessionMixin (Base) { /** * @returns {Promise} */ - async getSessions () { + async getSessions() { let ret = []; if (this.sessionId) { @@ -31,7 +31,7 @@ export function SessionMixin (Base) { /** * @returns {Promise} */ - async getSession () { + async getSession() { if (this.caps.eventTimings) { return {...this.caps, events: this.eventHistory}; } diff --git a/packages/base-driver/lib/basedriver/commands/settings.js b/packages/base-driver/lib/basedriver/commands/settings.js index 005f72b0c..b3638b862 100644 --- a/packages/base-driver/lib/basedriver/commands/settings.js +++ b/packages/base-driver/lib/basedriver/commands/settings.js @@ -5,26 +5,24 @@ * @param {ReturnType} Base * @returns {SettingsBase} */ -export function SettingsMixin (Base) { +export function SettingsMixin(Base) { /** * @implements {ISettingsCommands} */ class SettingsCommands extends Base { - - async updateSettings (newSettings) { + async updateSettings(newSettings) { if (!this.settings) { this.log.errorAndThrow('Cannot update settings; settings object not found'); } return await this.settings.update(newSettings); } - async getSettings () { + async getSettings() { if (!this.settings) { this.log.errorAndThrow('Cannot get settings; settings object not found'); } return await this.settings.getSettings(); } - } return SettingsCommands; diff --git a/packages/base-driver/lib/basedriver/commands/timeout.js b/packages/base-driver/lib/basedriver/commands/timeout.js index 7cfe3f90a..a9b107021 100644 --- a/packages/base-driver/lib/basedriver/commands/timeout.js +++ b/packages/base-driver/lib/basedriver/commands/timeout.js @@ -13,17 +13,14 @@ const MIN_TIMEOUT = 0; * @param {import('../driver').BaseDriverBase} Base * @returns {TimeoutBase} */ -export function TimeoutMixin (Base) { - +export function TimeoutMixin(Base) { /** * @implements {ITimeoutCommands} */ class TimeoutCommands extends Base { - async timeouts (type, ms, script, pageLoad, implicit) { + async timeouts(type, ms, script, pageLoad, implicit) { if (util.hasValue(type) && util.hasValue(ms)) { - this.log.debug( - `MJSONWP timeout arguments: ${JSON.stringify({type, ms})}}`, - ); + this.log.debug(`MJSONWP timeout arguments: ${JSON.stringify({type, ms})}}`); switch (type) { case 'command': @@ -39,9 +36,7 @@ export function TimeoutMixin (Base) { await this.scriptTimeoutMJSONWP(ms); return; default: - throw new Error( - `'${type}' type is not supported for MJSONWP timeout`, - ); + throw new Error(`'${type}' type is not supported for MJSONWP timeout`); } } @@ -51,7 +46,7 @@ export function TimeoutMixin (Base) { script, pageLoad, implicit, - })}}`, + })}}` ); if (util.hasValue(script)) { await this.scriptTimeoutW3C(script); @@ -64,7 +59,7 @@ export function TimeoutMixin (Base) { } } - async getTimeouts () { + async getTimeouts() { return { command: this.newCommandTimeoutMs, implicit: this.implicitWaitMs, @@ -72,42 +67,42 @@ export function TimeoutMixin (Base) { } // implicit - async implicitWaitW3C (ms) { + async implicitWaitW3C(ms) { await this.implicitWait(ms); } - async implicitWaitMJSONWP (ms) { + async implicitWaitMJSONWP(ms) { await this.implicitWait(ms); } - async implicitWait (ms) { + async implicitWait(ms) { await this.setImplicitWait(this.parseTimeoutArgument(ms)); } // pageLoad - async pageLoadTimeoutW3C (ms) { + async pageLoadTimeoutW3C(ms) { throw new errors.NotImplementedError('Not implemented yet for pageLoad.'); } - async pageLoadTimeoutMJSONWP (ms) { + async pageLoadTimeoutMJSONWP(ms) { throw new errors.NotImplementedError('Not implemented yet for pageLoad.'); } // script - async scriptTimeoutW3C (ms) { + async scriptTimeoutW3C(ms) { throw new errors.NotImplementedError('Not implemented yet for script.'); } - async scriptTimeoutMJSONWP (ms) { + async scriptTimeoutMJSONWP(ms) { throw new errors.NotImplementedError('Not implemented yet for script.'); } // command - async newCommandTimeout (ms) { + async newCommandTimeout(ms) { this.setNewCommandTimeout(this.parseTimeoutArgument(ms)); } - setImplicitWait (ms) { + setImplicitWait(ms) { // eslint-disable-line require-await this.implicitWaitMs = ms; this.log.debug(`Set implicit wait to ${ms}ms`); @@ -121,7 +116,7 @@ export function TimeoutMixin (Base) { } } - setNewCommandTimeout (ms) { + setNewCommandTimeout(ms) { this.newCommandTimeoutMs = ms; this.log.debug(`Set new command timeout to ${ms}ms`); if (this.managedDrivers && this.managedDrivers.length) { @@ -134,7 +129,7 @@ export function TimeoutMixin (Base) { } } - async implicitWaitForCondition (condFn) { + async implicitWaitForCondition(condFn) { this.log.debug(`Waiting up to ${this.implicitWaitMs} ms for condition`); let wrappedCondFn = async (...args) => { // reset command timeout @@ -149,7 +144,7 @@ export function TimeoutMixin (Base) { }); } - parseTimeoutArgument (ms) { + parseTimeoutArgument(ms) { let duration = parseInt(ms, 10); if (_.isNaN(duration) || duration < MIN_TIMEOUT) { throw new errors.UnknownError(`Invalid timeout value '${ms}'`); diff --git a/packages/base-driver/lib/basedriver/core.js b/packages/base-driver/lib/basedriver/core.js index 5a3bc4085..d3663cdf3 100644 --- a/packages/base-driver/lib/basedriver/core.js +++ b/packages/base-driver/lib/basedriver/core.js @@ -2,15 +2,15 @@ /* eslint-disable no-unused-vars */ /* eslint-disable require-await */ -import { fs, logger, node } from '@appium/support'; +import {fs, logger, node} from '@appium/support'; import AsyncLock from 'async-lock'; -import { EventEmitter } from 'events'; +import {EventEmitter} from 'events'; import _ from 'lodash'; import os from 'os'; -import { DEFAULT_BASE_PATH, PROTOCOLS } from '../constants'; -import { errors } from '../protocol'; -import { validateCaps } from './capabilities'; -import { desiredCapabilityConstraints } from './desired-caps'; +import {DEFAULT_BASE_PATH, PROTOCOLS} from '../constants'; +import {errors} from '../protocol'; +import {validateCaps} from './capabilities'; +import {desiredCapabilityConstraints} from './desired-caps'; import DeviceSettings from './device-settings'; import helpers from './helpers'; @@ -24,7 +24,6 @@ const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown'; * @implements {Core} */ class DriverCore { - /** * Make the basedriver version available so for any driver which inherits from this package, we * know which version of basedriver it inherited from @@ -32,40 +31,40 @@ class DriverCore { static baseVersion = BASEDRIVER_VER; /** - * @type {string?} - */ + * @type {string?} + */ sessionId = null; /** - * @type {DriverOpts & Capabilities} - */ + * @type {DriverOpts & Capabilities} + */ opts; /** - * @type {DriverOpts} - */ + * @type {DriverOpts} + */ initialOpts; /** - * @type {Capabilities} - */ + * @type {Capabilities} + */ caps; /** - * @type {W3CCapabilities} - */ + * @type {W3CCapabilities} + */ originalCaps; helpers = helpers; /** - * basePath is used for several purposes, for example in setting up - * proxying to other drivers, since we need to know what the base path - * of any incoming request might look like. We set it to the default - * initially but it is automatically updated during any actual program - * execution by the routeConfiguringFunction, which is necessarily run as - * the entrypoint for any Appium server - */ + * basePath is used for several purposes, for example in setting up + * proxying to other drivers, since we need to know what the base path + * of any incoming request might look like. We set it to the default + * initially but it is automatically updated during any actual program + * execution by the routeConfiguringFunction, which is necessarily run as + * the entrypoint for any Appium server + */ basePath = DEFAULT_BASE_PATH; relaxedSecurityEnabled = false; @@ -102,38 +101,35 @@ class DriverCore { eventEmitter = new EventEmitter(); /** - * @type {AppiumLogger} - */ + * @type {AppiumLogger} + */ _log; /** - * @protected - */ + * @protected + */ shutdownUnexpectedly = false; /** - * @type {boolean} - * @protected - */ + * @type {boolean} + * @protected + */ shouldValidateCaps; /** - * @protected - */ + * @protected + */ commandsQueueGuard = new AsyncLock(); /** - * settings should be instantiated by drivers which extend BaseDriver, but - * we set it to an empty DeviceSettings instance here to make sure that the - * default settings are applied even if an extending driver doesn't utilize - * the settings functionality itself - */ + * settings should be instantiated by drivers which extend BaseDriver, but + * we set it to an empty DeviceSettings instance here to make sure that the + * default settings are applied even if an extending driver doesn't utilize + * the settings functionality itself + */ settings = new DeviceSettings(); - constructor ( - opts = /** @type {DriverOpts} */ ({}), - shouldValidateCaps = true, - ) { + constructor(opts = /** @type {DriverOpts} */ ({}), shouldValidateCaps = true) { this._log = logger.getLogger(helpers.generateDriverLogPrefix(this)); // setup state @@ -141,8 +137,7 @@ class DriverCore { // use a custom tmp dir to avoid losing data and app when computer is // restarted - this.opts.tmpDir = - this.opts.tmpDir || process.env.APPIUM_TMP_DIR || os.tmpdir(); + this.opts.tmpDir = this.opts.tmpDir || process.env.APPIUM_TMP_DIR || os.tmpdir(); // base-driver internals this.shouldValidateCaps = shouldValidateCaps; @@ -153,63 +148,63 @@ class DriverCore { this.sessionId = null; } - get log () { + get log() { return this._log; } /** - * Set a callback handler if needed to execute a custom piece of code - * when the driver is shut down unexpectedly. Multiple calls to this method - * will cause the handler to be executed mutiple times - * - * @param {(...args: any[]) => void} handler The code to be executed on unexpected shutdown. - * The function may accept one argument, which is the actual error instance, which - * caused the driver to shut down. - */ - onUnexpectedShutdown (handler) { + * Set a callback handler if needed to execute a custom piece of code + * when the driver is shut down unexpectedly. Multiple calls to this method + * will cause the handler to be executed mutiple times + * + * @param {(...args: any[]) => void} handler The code to be executed on unexpected shutdown. + * The function may accept one argument, which is the actual error instance, which + * caused the driver to shut down. + */ + onUnexpectedShutdown(handler) { this.eventEmitter.on(ON_UNEXPECTED_SHUTDOWN_EVENT, handler); } /** - * This property is used by AppiumDriver to store the data of the - * specific driver sessions. This data can be later used to adjust - * properties for driver instances running in parallel. - * Override it in inherited driver classes if necessary. - * - * @return {Record} Driver properties mapping - */ - get driverData () { + * This property is used by AppiumDriver to store the data of the + * specific driver sessions. This data can be later used to adjust + * properties for driver instances running in parallel. + * Override it in inherited driver classes if necessary. + * + * @return {Record} Driver properties mapping + */ + get driverData() { return {}; } /** - * This property controls the way {#executeCommand} method - * handles new driver commands received from the client. - * Override it for inherited classes only in special cases. - * - * @return {boolean} If the returned value is true (default) then all the commands - * received by the particular driver instance are going to be put into the queue, - * so each following command will not be executed until the previous command - * execution is completed. False value disables that queue, so each driver command - * is executed independently and does not wait for anything. - */ - get isCommandsQueueEnabled () { + * This property controls the way {#executeCommand} method + * handles new driver commands received from the client. + * Override it for inherited classes only in special cases. + * + * @return {boolean} If the returned value is true (default) then all the commands + * received by the particular driver instance are going to be put into the queue, + * so each following command will not be executed until the previous command + * execution is completed. False value disables that queue, so each driver command + * is executed independently and does not wait for anything. + */ + get isCommandsQueueEnabled() { return true; } /* - * make eventHistory a property and return a cloned object so a consumer can't - * inadvertently change data outside of logEvent - */ - get eventHistory () { + * make eventHistory a property and return a cloned object so a consumer can't + * inadvertently change data outside of logEvent + */ + get eventHistory() { return _.cloneDeep(this._eventHistory); } /** - * API method for driver developers to log timings for important events - * @param {string} eventName - */ - logEvent (eventName) { + * API method for driver developers to log timings for important events + * @param {string} eventName + */ + logEvent(eventName) { if (eventName === 'commands') { throw new Error('Cannot log commands directly'); } @@ -226,15 +221,15 @@ class DriverCore { } /** - * Overridden in appium driver, but here so that individual drivers can be - * tested with clients that poll - */ - async getStatus () { + * Overridden in appium driver, but here so that individual drivers can be + * tested with clients that poll + */ + async getStatus() { return {}; } // we only want subclasses to ever extend the contraints - set desiredCapConstraints (constraints) { + set desiredCapConstraints(constraints) { this._constraints = Object.assign(this._constraints, constraints); // 'presence' means different things in different versions of the validator, // when we say 'true' we mean that it should not be able to be empty @@ -247,41 +242,40 @@ class DriverCore { } } - get desiredCapConstraints () { + get desiredCapConstraints() { return this._constraints; } /** - * method required by MJSONWP in order to determine whether it should - * respond with an invalid session response - * @param {string} [sessionId] - * @returns {boolean} - */ - sessionExists (sessionId) { + * method required by MJSONWP in order to determine whether it should + * respond with an invalid session response + * @param {string} [sessionId] + * @returns {boolean} + */ + sessionExists(sessionId) { if (!sessionId) return false; // eslint-disable-line curly return sessionId === this.sessionId; } /** - * method required by MJSONWP in order to determine if the command should - * be proxied directly to the driver - * @param {string} sessionId - * @returns {this | import('@appium/types').Driver} - */ - driverForSession (sessionId) { + * method required by MJSONWP in order to determine if the command should + * be proxied directly to the driver + * @param {string} sessionId + * @returns {this | import('@appium/types').Driver} + */ + driverForSession(sessionId) { return this; } /** - * - * @param {Capabilities} caps - */ - logExtraCaps (caps) { + * + * @param {Capabilities} caps + */ + logExtraCaps(caps) { let extraCaps = _.difference(_.keys(caps), _.keys(this._constraints)); if (extraCaps.length) { this.log.warn( - `The following capabilities were provided, but are not ` + - `recognized by Appium:`, + `The following capabilities were provided, but are not ` + `recognized by Appium:` ); for (const cap of extraCaps) { this.log.warn(` ${cap}`); @@ -290,11 +284,11 @@ class DriverCore { } /** - * - * @param {Capabilities} caps - * @returns {boolean} - */ - validateDesiredCaps (caps) { + * + * @param {Capabilities} caps + * @returns {boolean} + */ + validateDesiredCaps(caps) { if (!this.shouldValidateCaps) { return true; } @@ -305,8 +299,8 @@ class DriverCore { this.log.errorAndThrow( new errors.SessionNotCreatedError( `The desiredCapabilities object was not valid for the ` + - `following reason(s): ${e.message}`, - ), + `following reason(s): ${e.message}` + ) ); } @@ -315,30 +309,30 @@ class DriverCore { return true; } - isMjsonwpProtocol () { + isMjsonwpProtocol() { return this.protocol === PROTOCOLS.MJSONWP; } - isW3CProtocol () { + isW3CProtocol() { return this.protocol === PROTOCOLS.W3C; } - setProtocolMJSONWP () { + setProtocolMJSONWP() { this.protocol = PROTOCOLS.MJSONWP; } - setProtocolW3C () { + setProtocolW3C() { this.protocol = PROTOCOLS.W3C; } /** - * Check whether a given feature is enabled via its name - * - * @param {string} name - name of feature/command - * - * @returns {Boolean} - */ - isFeatureEnabled (name) { + * Check whether a given feature is enabled via its name + * + * @param {string} name - name of feature/command + * + * @returns {Boolean} + */ + isFeatureEnabled(name) { // if we have explicitly denied this feature, return false immediately if (this.denyInsecure && _.includes(this.denyInsecure, name)) { return false; @@ -360,35 +354,31 @@ class DriverCore { } /** - * Assert that a given feature is enabled and throw a helpful error if it's - * not - * - * @param {string} name - name of feature/command - */ - ensureFeatureEnabled (name) { + * Assert that a given feature is enabled and throw a helpful error if it's + * not + * + * @param {string} name - name of feature/command + */ + ensureFeatureEnabled(name) { if (!this.isFeatureEnabled(name)) { throw new Error( `Potentially insecure feature '${name}' has not been ` + `enabled. If you want to enable this feature and accept ` + `the security ramifications, please do so by following ` + `the documented instructions at https://github.com/appium` + - `/appium/blob/master/docs/en/writing-running-appium/security.md`, + `/appium/blob/master/docs/en/writing-running-appium/security.md` ); } } /** - * - * @param {string} strategy - * @param {boolean} [webContext] - */ - validateLocatorStrategy (strategy, webContext = false) { + * + * @param {string} strategy + * @param {boolean} [webContext] + */ + validateLocatorStrategy(strategy, webContext = false) { let validStrategies = this.locatorStrategies; - this.log.debug( - `Valid locator strategies for this request: ${validStrategies.join( - ', ', - )}`, - ); + this.log.debug(`Valid locator strategies for this request: ${validStrategies.join(', ')}`); if (webContext) { validStrategies = validStrategies.concat(this.webLocatorStrategies); @@ -396,52 +386,52 @@ class DriverCore { if (!_.includes(validStrategies, strategy)) { throw new errors.InvalidSelectorError( - `Locator Strategy '${strategy}' is not supported for this session`, + `Locator Strategy '${strategy}' is not supported for this session` ); } } /** - * - * @param {string} [sessionId] - * @returns {boolean} - */ - proxyActive (sessionId) { + * + * @param {string} [sessionId] + * @returns {boolean} + */ + proxyActive(sessionId) { return false; } /** - * - * @param {string} sessionId - * @returns {[string, RegExp][]} - */ - getProxyAvoidList (sessionId) { + * + * @param {string} sessionId + * @returns {[string, RegExp][]} + */ + getProxyAvoidList(sessionId) { return []; } /** - * - * @param {string} [sessionId] - * @returns {boolean} - */ - canProxy (sessionId) { + * + * @param {string} [sessionId] + * @returns {boolean} + */ + canProxy(sessionId) { return false; } /** - * Whether a given command route (expressed as method and url) should not be - * proxied according to this driver - * - * @param {string} sessionId - the current sessionId (in case the driver runs - * multiple session ids and requires it). This is not used in this method but - * should be made available to overridden methods. - * @param {import('@appium/types').HTTPMethod} method - HTTP method of the route - * @param {string} url - url of the route - * @param {any} [body] - webdriver request body - * - * @returns {boolean} - whether the route should be avoided - */ - proxyRouteIsAvoided (sessionId, method, url, body) { + * Whether a given command route (expressed as method and url) should not be + * proxied according to this driver + * + * @param {string} sessionId - the current sessionId (in case the driver runs + * multiple session ids and requires it). This is not used in this method but + * should be made available to overridden methods. + * @param {import('@appium/types').HTTPMethod} method - HTTP method of the route + * @param {string} url - url of the route + * @param {any} [body] - webdriver request body + * + * @returns {boolean} - whether the route should be avoided + */ + proxyRouteIsAvoided(sessionId, method, url, body) { for (let avoidSchema of this.getProxyAvoidList(sessionId)) { if (!_.isArray(avoidSchema) || avoidSchema.length !== 2) { throw new Error('Proxy avoidance must be a list of pairs'); @@ -453,10 +443,7 @@ class DriverCore { if (!_.isRegExp(avoidPathRegex)) { throw new Error('Proxy avoidance path must be a regular expression'); } - let normalizedUrl = url.replace( - new RegExp(`^${_.escapeRegExp(this.basePath)}`), - '', - ); + let normalizedUrl = url.replace(new RegExp(`^${_.escapeRegExp(this.basePath)}`), ''); if (avoidMethod === method && avoidPathRegex.test(normalizedUrl)) { return true; } @@ -465,18 +452,18 @@ class DriverCore { } /** - * - * @param {Driver} driver - */ - addManagedDriver (driver) { + * + * @param {Driver} driver + */ + addManagedDriver(driver) { this.managedDrivers.push(driver); } - getManagedDrivers () { + getManagedDrivers() { return this.managedDrivers; } - async clearNewCommandTimeout () { + async clearNewCommandTimeout() { if (this.noCommandTimer) { clearTimeout(this.noCommandTimer); this.noCommandTimer = null; diff --git a/packages/base-driver/lib/basedriver/desired-caps.js b/packages/base-driver/lib/basedriver/desired-caps.js index 32b05b4a0..6403d1fb9 100755 --- a/packages/base-driver/lib/basedriver/desired-caps.js +++ b/packages/base-driver/lib/basedriver/desired-caps.js @@ -9,53 +9,50 @@ let desiredCapabilityConstraints = { isString: true, }, deviceName: { - isString: true + isString: true, }, platformVersion: { - isString: true + isString: true, }, newCommandTimeout: { - isNumber: true + isNumber: true, }, automationName: { - isString: true + isString: true, }, autoLaunch: { - isBoolean: true + isBoolean: true, }, udid: { - isString: true + isString: true, }, orientation: { - inclusion: [ - 'LANDSCAPE', - 'PORTRAIT' - ] + inclusion: ['LANDSCAPE', 'PORTRAIT'], }, autoWebview: { - isBoolean: true + isBoolean: true, }, noReset: { - isBoolean: true + isBoolean: true, }, fullReset: { - isBoolean: true + isBoolean: true, }, language: { - isString: true + isString: true, }, locale: { - isString: true + isString: true, }, eventTimings: { - isBoolean: true + isBoolean: true, }, printPageSourceOnFindFailure: { - isBoolean: true + isBoolean: true, }, }; -validator.validators.isString = function isString (value) { +validator.validators.isString = function isString(value) { if (typeof value === 'string') { return null; } @@ -66,7 +63,7 @@ validator.validators.isString = function isString (value) { return 'must be of type string'; }; -validator.validators.isNumber = function isNumber (value) { +validator.validators.isNumber = function isNumber(value) { if (typeof value === 'number') { return null; } @@ -83,7 +80,7 @@ validator.validators.isNumber = function isNumber (value) { return 'must be of type number'; }; -validator.validators.isBoolean = function isBoolean (value) { +validator.validators.isBoolean = function isBoolean(value) { if (typeof value === 'boolean') { return null; } @@ -99,7 +96,7 @@ validator.validators.isBoolean = function isBoolean (value) { return 'must be of type boolean'; }; -validator.validators.isObject = function isObject (value) { +validator.validators.isObject = function isObject(value) { if (typeof value === 'object') { return null; } @@ -110,7 +107,7 @@ validator.validators.isObject = function isObject (value) { return 'must be of type object'; }; -validator.validators.isArray = function isArray (value) { +validator.validators.isArray = function isArray(value) { if (Array.isArray(value)) { return null; } @@ -121,13 +118,13 @@ validator.validators.isArray = function isArray (value) { return 'must be of type array'; }; -validator.validators.deprecated = function deprecated (value, options, key) { +validator.validators.deprecated = function deprecated(value, options, key) { if (options) { log.warn(`${key} is a deprecated capability`); } return null; }; -validator.validators.inclusionCaseInsensitive = function inclusionCaseInsensitive (value, options) { +validator.validators.inclusionCaseInsensitive = function inclusionCaseInsensitive(value, options) { if (typeof value === 'undefined') { return null; } else if (typeof value !== 'string') { @@ -142,9 +139,8 @@ validator.validators.inclusionCaseInsensitive = function inclusionCaseInsensitiv }; validator.promise = B; -validator.prettify = function prettify (val) { +validator.prettify = function prettify(val) { return val; }; - -export { desiredCapabilityConstraints, validator }; +export {desiredCapabilityConstraints, validator}; diff --git a/packages/base-driver/lib/basedriver/device-settings.js b/packages/base-driver/lib/basedriver/device-settings.js index e2d039c4e..bc70943f7 100644 --- a/packages/base-driver/lib/basedriver/device-settings.js +++ b/packages/base-driver/lib/basedriver/device-settings.js @@ -2,8 +2,8 @@ import _ from 'lodash'; import log from './logger'; -import { node, util } from '@appium/support'; -import { errors } from '../protocol/errors'; +import {node, util} from '@appium/support'; +import {errors} from '../protocol/errors'; /** * Maximum size (in bytes) of a given driver's settings object (which is internal to {@linkcode DriverSettings}). @@ -15,7 +15,6 @@ export const MAX_SETTINGS_SIZE = 20 * 1024 * 1024; // 20 MB * @implements {IDeviceSettings} */ class DeviceSettings { - /** * @protected * @type {T} @@ -33,8 +32,8 @@ class DeviceSettings { * @param {T} [defaultSettings] * @param {import('@appium/types').SettingsUpdateListener} [onSettingsUpdate] */ - constructor (defaultSettings, onSettingsUpdate) { - this._settings = /** @type {T} */({...(defaultSettings ?? {})}); + constructor(defaultSettings, onSettingsUpdate) { + this._settings = /** @type {T} */ ({...(defaultSettings ?? {})}); this._onSettingsUpdate = onSettingsUpdate ?? (async () => {}); } @@ -42,18 +41,22 @@ class DeviceSettings { * calls updateSettings from implementing driver every time a setting is changed. * @param {T} newSettings */ - async update (newSettings) { + async update(newSettings) { if (!_.isPlainObject(newSettings)) { - throw new errors.InvalidArgumentError(`Settings update should be called with valid JSON. Got ` + - `${JSON.stringify(newSettings)} instead`); + throw new errors.InvalidArgumentError( + `Settings update should be called with valid JSON. Got ` + + `${JSON.stringify(newSettings)} instead` + ); } if (node.getObjectSize({...this._settings, ...newSettings}) >= MAX_SETTINGS_SIZE) { - throw new errors.InvalidArgumentError(`New settings cannot be applied, because the overall ` + - `object size exceeds the allowed limit of ${util.toReadableSizeString(MAX_SETTINGS_SIZE)}`); + throw new errors.InvalidArgumentError( + `New settings cannot be applied, because the overall ` + + `object size exceeds the allowed limit of ${util.toReadableSizeString(MAX_SETTINGS_SIZE)}` + ); } - const props = /** @type {(keyof T & string)[]} */(_.keys(newSettings)); + const props = /** @type {(keyof T & string)[]} */ (_.keys(newSettings)); for (const prop of props) { if (!_.isUndefined(this._settings[prop])) { if (this._settings[prop] === newSettings[prop]) { @@ -66,13 +69,13 @@ class DeviceSettings { } } - getSettings () { + getSettings() { return this._settings; } } export default DeviceSettings; -export { DeviceSettings }; +export {DeviceSettings}; /** * @template T diff --git a/packages/base-driver/lib/basedriver/driver.js b/packages/base-driver/lib/basedriver/driver.js index 407f43d8f..c61770e7d 100644 --- a/packages/base-driver/lib/basedriver/driver.js +++ b/packages/base-driver/lib/basedriver/driver.js @@ -2,19 +2,19 @@ /* eslint-disable require-await */ /* eslint-disable no-unused-vars */ -import { DriverCore } from './core'; -import { util } from '@appium/support'; +import {DriverCore} from './core'; +import {util} from '@appium/support'; import B from 'bluebird'; import _ from 'lodash'; -import { fixCaps, isW3cCaps } from '../helpers/capabilities'; -import { DELETE_SESSION_COMMAND, determineProtocol, errors } from '../protocol'; +import {fixCaps, isW3cCaps} from '../helpers/capabilities'; +import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol'; import { APPIUM_OPTS_CAP, PREFIXED_APPIUM_OPTS_CAP, processCapabilities, - promoteAppiumOptions + promoteAppiumOptions, } from './capabilities'; -import { createBaseDriverClass } from './commands'; +import {createBaseDriverClass} from './commands'; import helpers from './helpers'; const EVENT_SESSION_INIT = 'newSessionRequested'; @@ -23,12 +23,10 @@ const EVENT_SESSION_QUIT_START = 'quitSessionRequested'; const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished'; const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown'; - /** * @implements {SessionHandler} */ export class BaseDriverCore extends DriverCore { - /** @type {Record|undefined} */ cliArgs; @@ -37,11 +35,11 @@ export class BaseDriverCore extends DriverCore { // and ensuring that we execute commands one at a time. This method is called // by MJSONWP's express router. /** - * @param {string} cmd - * @param {...any[]} args - * @returns {Promise} - */ - async executeCommand (cmd, ...args) { + * @param {string} cmd + * @param {...any[]} args + * @returns {Promise} + */ + async executeCommand(cmd, ...args) { // get start time for this command, and log in special cases let startTime = Date.now(); @@ -58,9 +56,7 @@ export class BaseDriverCore extends DriverCore { await this.clearNewCommandTimeout(); if (this.shutdownUnexpectedly) { - throw new errors.NoSuchDriverError( - 'The driver was unexpectedly shut down!', - ); + throw new errors.NoSuchDriverError('The driver was unexpectedly shut down!'); } // If we don't have this command, it must not be implemented @@ -74,17 +70,14 @@ export class BaseDriverCore extends DriverCore { this[cmd](...args), new B((resolve, reject) => { unexpectedShutdownListener = reject; - this.eventEmitter.on( - ON_UNEXPECTED_SHUTDOWN_EVENT, - unexpectedShutdownListener, - ); + this.eventEmitter.on(ON_UNEXPECTED_SHUTDOWN_EVENT, unexpectedShutdownListener); }), ]).finally(() => { if (unexpectedShutdownListener) { // This is needed to prevent memory leaks this.eventEmitter.removeListener( - ON_UNEXPECTED_SHUTDOWN_EVENT, - unexpectedShutdownListener, + ON_UNEXPECTED_SHUTDOWN_EVENT, + unexpectedShutdownListener ); unexpectedShutdownListener = null; } @@ -117,13 +110,11 @@ export class BaseDriverCore extends DriverCore { } /** - * - * @param {Error} err - */ - async startUnexpectedShutdown ( - err = new errors.NoSuchDriverError( - 'The driver was unexpectedly shut down!', - ), + * + * @param {Error} err + */ + async startUnexpectedShutdown( + err = new errors.NoSuchDriverError('The driver was unexpectedly shut down!') ) { this.eventEmitter.emit(ON_UNEXPECTED_SHUTDOWN_EVENT, err); // allow others to listen for this this.shutdownUnexpectedly = true; @@ -136,7 +127,7 @@ export class BaseDriverCore extends DriverCore { } } - async startNewCommandTimeout () { + async startNewCommandTimeout() { // make sure there are no rogue timeouts await this.clearNewCommandTimeout(); @@ -146,7 +137,7 @@ export class BaseDriverCore extends DriverCore { this.noCommandTimer = setTimeout(async () => { this.log.warn( `Shutting down because we waited ` + - `${this.newCommandTimeoutMs / 1000.0} seconds for a command`, + `${this.newCommandTimeoutMs / 1000.0} seconds for a command` ); const errorMessage = `New Command Timeout of ` + @@ -164,7 +155,7 @@ export class BaseDriverCore extends DriverCore { * @param {number} port * @param {string} path */ - assignServer (server, host, port, path) { + assignServer(server, host, port, path) { this.server = server; this.serverHost = host; this.serverPort = port; @@ -172,10 +163,10 @@ export class BaseDriverCore extends DriverCore { } /* - * Restart the session with the original caps, - * preserving the timeout config. - */ - async reset () { + * Restart the session with the original caps, + * preserving the timeout config. + */ + async reset() { this.log.debug('Resetting app mid-session'); this.log.debug('Running generic full reset'); @@ -209,40 +200,33 @@ export class BaseDriverCore extends DriverCore { } /** - * - * Historically the first two arguments were reserved for JSONWP capabilities. - * Appium 2 has dropped the support of these, so now we only accept capability - * objects in W3C format and thus allow any of the three arguments to represent - * the latter. - * @param {W3CCapabilities} w3cCapabilities1 - * @param {W3CCapabilities} [w3cCapabilities2] - * @param {W3CCapabilities} [w3cCapabilities] - * @param {DriverData[]} [driverData] - * @returns {Promise<[string,object]>} - */ - async createSession ( - w3cCapabilities1, - w3cCapabilities2, - w3cCapabilities, - driverData, - ) { + * + * Historically the first two arguments were reserved for JSONWP capabilities. + * Appium 2 has dropped the support of these, so now we only accept capability + * objects in W3C format and thus allow any of the three arguments to represent + * the latter. + * @param {W3CCapabilities} w3cCapabilities1 + * @param {W3CCapabilities} [w3cCapabilities2] + * @param {W3CCapabilities} [w3cCapabilities] + * @param {DriverData[]} [driverData] + * @returns {Promise<[string,object]>} + */ + async createSession(w3cCapabilities1, w3cCapabilities2, w3cCapabilities, driverData) { if (this.sessionId !== null) { throw new errors.SessionNotCreatedError( - 'Cannot create a new session while one is in progress', + 'Cannot create a new session while one is in progress' ); } this.log.debug(); - const originalCaps = _.cloneDeep([ - w3cCapabilities, - w3cCapabilities1, - w3cCapabilities2, - ].find(isW3cCaps)); + const originalCaps = _.cloneDeep( + [w3cCapabilities, w3cCapabilities1, w3cCapabilities2].find(isW3cCaps) + ); if (!originalCaps) { throw new errors.SessionNotCreatedError( - 'Appium only supports W3C-style capability objects. ' + - 'Your client is sending an older capabilities format. Please update your client library.', + 'Appium only supports W3C-style capability objects. ' + + 'Your client is sending an older capabilities format. Please update your client library.' ); } @@ -250,23 +234,15 @@ export class BaseDriverCore extends DriverCore { this.originalCaps = _.cloneDeep(originalCaps); this.log.debug( - `Creating session with W3C capabilities: ${JSON.stringify( - originalCaps, - null, - 2, - )}`, + `Creating session with W3C capabilities: ${JSON.stringify(originalCaps, null, 2)}` ); let caps; try { - caps = processCapabilities( - originalCaps, - this.desiredCapConstraints, - this.shouldValidateCaps, - ); + caps = processCapabilities(originalCaps, this.desiredCapConstraints, this.shouldValidateCaps); if (caps[APPIUM_OPTS_CAP]) { this.log.debug( - `Found ${PREFIXED_APPIUM_OPTS_CAP} capability present; will promote items inside to caps`, + `Found ${PREFIXED_APPIUM_OPTS_CAP} capability present; will promote items inside to caps` ); caps = promoteAppiumOptions(caps); } @@ -289,9 +265,9 @@ export class BaseDriverCore extends DriverCore { // both to true, but this is misguided and strange, so error here instead if (this.opts.noReset && this.opts.fullReset) { throw new Error( - "The 'noReset' and 'fullReset' capabilities are mutually " + - 'exclusive and should not both be set to true. You ' + - "probably meant to just use 'fullReset' on its own", + "The 'noReset' and 'fullReset' capabilities are mutually " + + 'exclusive and should not both be set to true. You ' + + "probably meant to just use 'fullReset' on its own" ); } if (this.opts.noReset === true) { @@ -320,12 +296,12 @@ export class BaseDriverCore extends DriverCore { } /** - * - * @param {string} [sessionId] - * @param {DriverData[]} [driverData] - * @returns {Promise} - */ - async deleteSession (sessionId, driverData) { + * + * @param {string} [sessionId] + * @param {DriverData[]} [driverData] + * @returns {Promise} + */ + async deleteSession(sessionId, driverData) { await this.clearNewCommandTimeout(); if (this.isCommandsQueueEnabled && this.commandsQueueGuard.isBusy()) { // simple hack to release pending commands if they exist @@ -345,7 +321,7 @@ export class BaseDriverCore extends DriverCore { * @implements {Driver} */ class BaseDriver extends createBaseDriverClass(BaseDriverCore) {} -export { BaseDriver }; +export {BaseDriver}; export default BaseDriver; /** @@ -357,7 +333,6 @@ export default BaseDriver; * @typedef {import('@appium/types').DriverData} DriverData */ - /** * @callback UpdateServerCallback * @param {import('express').Express} app - Express app diff --git a/packages/base-driver/lib/basedriver/helpers.js b/packages/base-driver/lib/basedriver/helpers.js index 31cfa4331..91145925d 100644 --- a/packages/base-driver/lib/basedriver/helpers.js +++ b/packages/base-driver/lib/basedriver/helpers.js @@ -2,18 +2,14 @@ import _ from 'lodash'; import path from 'path'; import url from 'url'; import logger from './logger'; -import { tempDir, fs, util, zip, net, timing, node } from '@appium/support'; +import {tempDir, fs, util, zip, net, timing, node} from '@appium/support'; import LRU from 'lru-cache'; import AsyncLock from 'async-lock'; import axios from 'axios'; const IPA_EXT = '.ipa'; const ZIP_EXTS = ['.zip', IPA_EXT]; -const ZIP_MIME_TYPES = [ - 'application/zip', - 'application/x-zip-compressed', - 'multipart/x-zip', -]; +const ZIP_MIME_TYPES = ['application/zip', 'application/x-zip-compressed', 'multipart/x-zip']; const CACHED_APPS_MAX_AGE = 1000 * 60 * 60 * 24; // ms const MAX_CACHED_APPS = 1024; const APPLICATIONS_CACHE = new LRU({ @@ -21,8 +17,10 @@ const APPLICATIONS_CACHE = new LRU({ ttl: CACHED_APPS_MAX_AGE, // expire after 24 hours updateAgeOnGet: true, dispose: (app, {fullPath}) => { - logger.info(`The application '${app}' cached at '${fullPath}' has ` + - `expired after ${CACHED_APPS_MAX_AGE}ms`); + logger.info( + `The application '${app}' cached at '${fullPath}' has ` + + `expired after ${CACHED_APPS_MAX_AGE}ms` + ); if (fullPath) { fs.rimraf(fullPath); } @@ -39,10 +37,11 @@ process.on('exit', () => { return; } - const appPaths = [...APPLICATIONS_CACHE.values()] - .map(({fullPath}) => fullPath); - logger.debug(`Performing cleanup of ${appPaths.length} cached ` + - util.pluralize('application', appPaths.length)); + const appPaths = [...APPLICATIONS_CACHE.values()].map(({fullPath}) => fullPath); + logger.debug( + `Performing cleanup of ${appPaths.length} cached ` + + util.pluralize('application', appPaths.length) + ); for (const appPath of appPaths) { try { // Asynchronous calls are not supported in onExit handler @@ -53,21 +52,22 @@ process.on('exit', () => { } }); - -async function retrieveHeaders (link) { +async function retrieveHeaders(link) { try { - return (await axios({ - url: link, - method: 'HEAD', - timeout: 5000, - })).headers; + return ( + await axios({ + url: link, + method: 'HEAD', + timeout: 5000, + }) + ).headers; } catch (e) { logger.info(`Cannot send HEAD request to '${link}'. Original error: ${e.message}`); } return {}; } -function getCachedApplicationPath (link, currentAppProps = {}, cachedAppInfo = {}) { +function getCachedApplicationPath(link, currentAppProps = {}, cachedAppInfo = {}) { const refresh = () => { logger.debug(`A fresh copy of the application is going to be downloaded from ${link}`); return null; @@ -108,7 +108,9 @@ function getCachedApplicationPath (link, currentAppProps = {}, cachedAppInfo = { if (currentMaxAge && timestamp) { const msLeft = timestamp + currentMaxAge * 1000 - Date.now(); if (msLeft > 0) { - logger.debug(`The cached application '${path.basename(fullPath)}' will expire in ${msLeft / 1000}s`); + logger.debug( + `The cached application '${path.basename(fullPath)}' will expire in ${msLeft / 1000}s` + ); return fullPath; } logger.debug(`The cached application '${path.basename(fullPath)}' has expired`); @@ -116,25 +118,27 @@ function getCachedApplicationPath (link, currentAppProps = {}, cachedAppInfo = { return refresh(); } -function verifyAppExtension (app, supportedAppExtensions) { +function verifyAppExtension(app, supportedAppExtensions) { if (supportedAppExtensions.map(_.toLower).includes(_.toLower(path.extname(app)))) { return app; } - throw new Error(`New app path '${app}' did not have ` + - `${util.pluralize('extension', supportedAppExtensions.length, false)}: ` + - supportedAppExtensions); + throw new Error( + `New app path '${app}' did not have ` + + `${util.pluralize('extension', supportedAppExtensions.length, false)}: ` + + supportedAppExtensions + ); } -async function calculateFolderIntegrity (folderPath) { +async function calculateFolderIntegrity(folderPath) { return (await fs.glob('**/*', {cwd: folderPath, strict: false, nosort: true})).length; } -async function calculateFileIntegrity (filePath) { +async function calculateFileIntegrity(filePath) { return await fs.hash(filePath); } -async function isAppIntegrityOk (currentPath, expectedIntegrity = {}) { - if (!await fs.exists(currentPath)) { +async function isAppIntegrityOk(currentPath, expectedIntegrity = {}) { + if (!(await fs.exists(currentPath))) { return false; } @@ -146,8 +150,8 @@ async function isAppIntegrityOk (currentPath, expectedIntegrity = {}) { // more precise, but we don't need to be very precise here and also don't want to // overuse RAM and have a performance drop. return (await fs.stat(currentPath)).isDirectory() - ? await calculateFolderIntegrity(currentPath) >= expectedIntegrity?.folder - : await calculateFileIntegrity(currentPath) === expectedIntegrity?.file; + ? (await calculateFolderIntegrity(currentPath)) >= expectedIntegrity?.folder + : (await calculateFileIntegrity(currentPath)) === expectedIntegrity?.file; } /** @@ -200,16 +204,14 @@ async function isAppIntegrityOk (currentPath, expectedIntegrity = {}) { * @param {string|string[]|ConfigureAppOptions} options * @returns The full path to the resulting application bundle */ -async function configureApp (app, options = {}) { +async function configureApp(app, options = {}) { if (!_.isString(app)) { // immediately shortcircuit if not given an app return; } let supportedAppExtensions; - const { - onPostProcess, - } = _.isPlainObject(options) ? options : {}; + const {onPostProcess} = _.isPlainObject(options) ? options : {}; if (_.isString(options)) { supportedAppExtensions = [options]; } else if (_.isArray(options)) { @@ -260,14 +262,16 @@ async function configureApp (app, options = {}) { logger.info(`Reusing previously downloaded application at '${cachedPath}'`); return verifyAppExtension(cachedPath, supportedAppExtensions); } - logger.info(`The application at '${cachedPath}' does not exist anymore ` + - `or its integrity has been damaged. Deleting it from the internal cache`); + logger.info( + `The application at '${cachedPath}' does not exist anymore ` + + `or its integrity has been damaged. Deleting it from the internal cache` + ); APPLICATIONS_CACHE.delete(app); } let fileName = null; const basename = fs.sanitizeName(path.basename(decodeURIComponent(pathname)), { - replacement: SANITIZE_REPLACEMENT + replacement: SANITIZE_REPLACEMENT, }); const extname = path.extname(basename); // to determine if we need to unzip the app, we have a number of places @@ -280,7 +284,11 @@ async function configureApp (app, options = {}) { const ct = headers['content-type']; logger.debug(`Content-Type: ${ct}`); // the filetype may not be obvious for certain urls, so check the mime type too - if (ZIP_MIME_TYPES.some((mimeType) => new RegExp(`\\b${_.escapeRegExp(mimeType)}\\b`).test(ct))) { + if ( + ZIP_MIME_TYPES.some((mimeType) => + new RegExp(`\\b${_.escapeRegExp(mimeType)}\\b`).test(ct) + ) + ) { if (!fileName) { fileName = `${DEFAULT_BASENAME}.zip`; } @@ -292,7 +300,7 @@ async function configureApp (app, options = {}) { const match = /filename="([^"]+)/i.exec(headers['content-disposition']); if (match) { fileName = fs.sanitizeName(match[1], { - replacement: SANITIZE_REPLACEMENT + replacement: SANITIZE_REPLACEMENT, }); shouldUnzipApp = shouldUnzipApp || ZIP_EXTS.includes(path.extname(fileName)); } @@ -304,8 +312,10 @@ async function configureApp (app, options = {}) { : DEFAULT_BASENAME; let resultingExt = extname; if (!supportedAppExtensions.includes(resultingExt)) { - logger.info(`The current file extension '${resultingExt}' is not supported. ` + - `Defaulting to '${_.first(supportedAppExtensions)}'`); + logger.info( + `The current file extension '${resultingExt}' is not supported. ` + + `Defaulting to '${_.first(supportedAppExtensions)}'` + ); resultingExt = _.first(supportedAppExtensions); } fileName = `${resultingName}${resultingExt}`; @@ -323,7 +333,8 @@ async function configureApp (app, options = {}) { let errorMessage = `The application at '${newApp}' does not exist or is not accessible`; // protocol value for 'C:\\temp' is 'c:', so we check the length as well if (_.isString(protocol) && protocol.length > 2) { - errorMessage = `The protocol '${protocol}' used in '${newApp}' is not supported. ` + + errorMessage = + `The protocol '${protocol}' used in '${newApp}' is not supported. ` + `Only http: and https: protocols are supported`; } throw new Error(errorMessage); @@ -345,8 +356,10 @@ async function configureApp (app, options = {}) { logger.info(`Will reuse previously cached application at '${fullPath}'`); return verifyAppExtension(fullPath, supportedAppExtensions); } - logger.info(`The application at '${fullPath}' does not exist anymore ` + - `or its integrity has been damaged. Deleting it from the cache`); + logger.info( + `The application at '${fullPath}' does not exist anymore ` + + `or its integrity has been damaged. Deleting it from the cache` + ); APPLICATIONS_CACHE.delete(app); } const tmpRoot = await tempDir.openDir(); @@ -360,8 +373,10 @@ async function configureApp (app, options = {}) { logger.info(`Unzipped local app to '${newApp}'`); } else if (!path.isAbsolute(newApp)) { newApp = path.resolve(process.cwd(), newApp); - logger.warn(`The current application path '${app}' is not absolute ` + - `and has been rewritten to '${newApp}'. Consider using absolute paths rather than relative`); + logger.warn( + `The current application path '${app}' is not absolute ` + + `and has been rewritten to '${newApp}'. Consider using absolute paths rather than relative` + ); app = newApp; } @@ -393,19 +408,19 @@ async function configureApp (app, options = {}) { headers: _.clone(headers), appPath: newApp, }); - return (!result?.appPath || app === result?.appPath || !await fs.exists(result?.appPath)) + return !result?.appPath || app === result?.appPath || !(await fs.exists(result?.appPath)) ? newApp : await storeAppInCache(result.appPath); } verifyAppExtension(newApp, supportedAppExtensions); - return (app !== newApp && (packageHash || _.values(remoteAppProps).some(Boolean))) + return app !== newApp && (packageHash || _.values(remoteAppProps).some(Boolean)) ? await storeAppInCache(newApp) : newApp; }); } -async function downloadApp (app, targetPath) { +async function downloadApp(app, targetPath) { const {href} = url.parse(app); try { await net.downloadFile(href, targetPath, { @@ -430,7 +445,7 @@ async function downloadApp (app, targetPath) { * @throws {Error} If the given archive is invalid or no application bundles * have been found inside */ -async function unzipApp (zipPath, dstRoot, supportedAppExtensions) { +async function unzipApp(zipPath, dstRoot, supportedAppExtensions) { await zip.assertValidZip(zipPath); if (!_.isArray(supportedAppExtensions)) { @@ -442,8 +457,8 @@ async function unzipApp (zipPath, dstRoot, supportedAppExtensions) { logger.debug(`Unzipping '${zipPath}'`); const timer = new timing.Timer().start(); const useSystemUnzipEnv = process.env.APPIUM_PREFER_SYSTEM_UNZIP; - const useSystemUnzip = _.isEmpty(useSystemUnzipEnv) - || !['0', 'false'].includes(_.toLower(useSystemUnzipEnv)); + const useSystemUnzip = + _.isEmpty(useSystemUnzipEnv) || !['0', 'false'].includes(_.toLower(useSystemUnzipEnv)); /** * Attempt to use use the system `unzip` (e.g., `/usr/bin/unzip`) due * to the significant performance improvement it provides over the native @@ -453,24 +468,40 @@ async function unzipApp (zipPath, dstRoot, supportedAppExtensions) { const extractionOpts = {useSystemUnzip}; // https://github.com/appium/appium/issues/14100 if (path.extname(zipPath) === IPA_EXT) { - logger.debug(`Enforcing UTF-8 encoding on the extracted file names for '${path.basename(zipPath)}'`); + logger.debug( + `Enforcing UTF-8 encoding on the extracted file names for '${path.basename(zipPath)}'` + ); extractionOpts.fileNamesEncoding = 'utf8'; } await zip.extractAllTo(zipPath, tmpRoot, extractionOpts); - const globPattern = `**/*.+(${supportedAppExtensions.map((ext) => ext.replace(/^\./, '')).join('|')})`; - const sortedBundleItems = (await fs.glob(globPattern, { - cwd: tmpRoot, - strict: false, - // Get the top level match - })).sort((a, b) => a.split(path.sep).length - b.split(path.sep).length); + const globPattern = `**/*.+(${supportedAppExtensions + .map((ext) => ext.replace(/^\./, '')) + .join('|')})`; + const sortedBundleItems = ( + await fs.glob(globPattern, { + cwd: tmpRoot, + strict: false, + // Get the top level match + }) + ).sort((a, b) => a.split(path.sep).length - b.split(path.sep).length); if (_.isEmpty(sortedBundleItems)) { - logger.errorAndThrow(`App unzipped OK, but we could not find any '${supportedAppExtensions}' ` + - util.pluralize('bundle', supportedAppExtensions.length, false) + - ` in it. Make sure your archive contains at least one package having ` + - `'${supportedAppExtensions}' ${util.pluralize('extension', supportedAppExtensions.length, false)}`); + logger.errorAndThrow( + `App unzipped OK, but we could not find any '${supportedAppExtensions}' ` + + util.pluralize('bundle', supportedAppExtensions.length, false) + + ` in it. Make sure your archive contains at least one package having ` + + `'${supportedAppExtensions}' ${util.pluralize( + 'extension', + supportedAppExtensions.length, + false + )}` + ); } - logger.debug(`Extracted ${util.pluralize('bundle item', sortedBundleItems.length, true)} ` + - `from '${zipPath}' in ${Math.round(timer.getDuration().asMilliSeconds)}ms: ${sortedBundleItems}`); + logger.debug( + `Extracted ${util.pluralize('bundle item', sortedBundleItems.length, true)} ` + + `from '${zipPath}' in ${Math.round( + timer.getDuration().asMilliSeconds + )}ms: ${sortedBundleItems}` + ); const matchedBundle = _.first(sortedBundleItems); logger.info(`Assuming '${matchedBundle}' is the correct bundle`); const dstPath = path.resolve(dstRoot, path.basename(matchedBundle)); @@ -481,8 +512,8 @@ async function unzipApp (zipPath, dstRoot, supportedAppExtensions) { } } -function isPackageOrBundle (app) { - return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app); +function isPackageOrBundle(app) { + return /^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/.test(app); } /** @@ -495,7 +526,7 @@ function isPackageOrBundle (app) { * @param {String} firstKey The first key to duplicate * @param {String} secondKey The second key to duplicate */ -function duplicateKeys (input, firstKey, secondKey) { +function duplicateKeys(input, firstKey, secondKey) { // If array provided, recursively call on all elements if (_.isArray(input)) { return input.map((item) => duplicateKeys(item, firstKey, secondKey)); @@ -526,7 +557,7 @@ function duplicateKeys (input, firstKey, secondKey) { * * @param {string|Array} cap A desired capability */ -function parseCapsArray (cap) { +function parseCapsArray(cap) { if (_.isArray(cap)) { return cap; } @@ -553,15 +584,17 @@ function parseCapsArray (cap) { * @param {string?} sessionId session identifier (if exists) * @returns {string} */ -function generateDriverLogPrefix (obj, sessionId = null) { +function generateDriverLogPrefix(obj, sessionId = null) { const instanceName = `${obj.constructor.name}@${node.getObjectId(obj).substring(0, 4)}`; return sessionId ? `${instanceName} (${sessionId.substring(0, 8)})` : instanceName; } /** @type {import('@appium/types').DriverHelpers} */ export default { - configureApp, isPackageOrBundle, duplicateKeys, parseCapsArray, generateDriverLogPrefix -}; -export { - configureApp, isPackageOrBundle, duplicateKeys, parseCapsArray, generateDriverLogPrefix + configureApp, + isPackageOrBundle, + duplicateKeys, + parseCapsArray, + generateDriverLogPrefix, }; +export {configureApp, isPackageOrBundle, duplicateKeys, parseCapsArray, generateDriverLogPrefix}; diff --git a/packages/base-driver/lib/basedriver/logger.js b/packages/base-driver/lib/basedriver/logger.js index 0ff143e9e..a27468683 100644 --- a/packages/base-driver/lib/basedriver/logger.js +++ b/packages/base-driver/lib/basedriver/logger.js @@ -1,4 +1,4 @@ -import { logger } from '@appium/support'; +import {logger} from '@appium/support'; const log = logger.getLogger('BaseDriver'); export default log; diff --git a/packages/base-driver/lib/constants.js b/packages/base-driver/lib/constants.js index 6218d76e0..827921277 100644 --- a/packages/base-driver/lib/constants.js +++ b/packages/base-driver/lib/constants.js @@ -1,4 +1,4 @@ -import { util } from '@appium/support'; +import {util} from '@appium/support'; // The default maximum length of a single log record // containing http request/response body @@ -17,8 +17,4 @@ const PROTOCOLS = { // Before Appium 2.0, this default value was '/wd/hub' by historical reasons. const DEFAULT_BASE_PATH = ''; - -export { - MAX_LOG_BODY_LENGTH, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, - PROTOCOLS, DEFAULT_BASE_PATH -}; +export {MAX_LOG_BODY_LENGTH, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS, DEFAULT_BASE_PATH}; diff --git a/packages/base-driver/lib/express/crash.js b/packages/base-driver/lib/express/crash.js index 2ec90e0ed..4a3b9f882 100644 --- a/packages/base-driver/lib/express/crash.js +++ b/packages/base-driver/lib/express/crash.js @@ -1,13 +1,11 @@ -import { errors } from '../protocol'; +import {errors} from '../protocol'; - -function produceError () { +function produceError() { throw new errors.UnknownCommandError('Produced generic error for testing'); } -function produceCrash () { +function produceCrash() { throw new Error('We just tried to crash Appium!'); } - -export { produceError, produceCrash }; +export {produceError, produceCrash}; diff --git a/packages/base-driver/lib/express/express-logging.js b/packages/base-driver/lib/express/express-logging.js index 9a0324c75..9a53cbeb9 100644 --- a/packages/base-driver/lib/express/express-logging.js +++ b/packages/base-driver/lib/express/express-logging.js @@ -2,23 +2,21 @@ import _ from 'lodash'; import '@colors/colors'; import morgan from 'morgan'; import log from './logger'; -import { MAX_LOG_BODY_LENGTH } from '../constants'; - +import {MAX_LOG_BODY_LENGTH} from '../constants'; // Copied the morgan compile function over so that cooler formats // may be configured -function compile (fmt) { +function compile(fmt) { // escape quotes fmt = fmt.replace(/"/g, '\\"'); - fmt = fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, - function replace (_, name, arg) { - return `"\n + (tokens["${name}"](req, res, "${arg}") || "-") + "`; - }); + fmt = fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function replace(_, name, arg) { + return `"\n + (tokens["${name}"](req, res, "${arg}") || "-") + "`; + }); let js = ` return "${fmt}";`; return new Function('tokens, req, res', js); } -function requestEndLoggingFormat (tokens, req, res) { +function requestEndLoggingFormat(tokens, req, res) { let status = res.statusCode; let statusStr = ':status'; if (status >= 500) { @@ -30,28 +28,32 @@ function requestEndLoggingFormat (tokens, req, res) { } else { statusStr = statusStr.green; } - let fn = compile(`${'<-- :method :url '.white}${statusStr} ${':response-time ms - :res[content-length]'.grey}`); + let fn = compile( + `${'<-- :method :url '.white}${statusStr} ${':response-time ms - :res[content-length]'.grey}` + ); return fn(tokens, req, res); } const endLogFormatter = morgan((tokens, req, res) => { - log.info(requestEndLoggingFormat(tokens, req, res), - (res.jsonResp || '').grey); + log.info(requestEndLoggingFormat(tokens, req, res), (res.jsonResp || '').grey); }); const requestStartLoggingFormat = compile(`${'-->'.white} ${':method'.white} ${':url'.white}`); -const startLogFormatter = morgan((tokens, req, res) => { - // morgan output is redirected straight to winston - let reqBody = ''; - if (req.body) { - try { - reqBody = _.truncate(_.isString(req.body) ? req.body : JSON.stringify(req.body), { - length: MAX_LOG_BODY_LENGTH, - }); - } catch (ign) {} - } - log.info(requestStartLoggingFormat(tokens, req, res), reqBody.grey); -}, {immediate: true}); +const startLogFormatter = morgan( + (tokens, req, res) => { + // morgan output is redirected straight to winston + let reqBody = ''; + if (req.body) { + try { + reqBody = _.truncate(_.isString(req.body) ? req.body : JSON.stringify(req.body), { + length: MAX_LOG_BODY_LENGTH, + }); + } catch (ign) {} + } + log.info(requestStartLoggingFormat(tokens, req, res), reqBody.grey); + }, + {immediate: true} +); -export { endLogFormatter, startLogFormatter }; +export {endLogFormatter, startLogFormatter}; diff --git a/packages/base-driver/lib/express/idempotency.js b/packages/base-driver/lib/express/idempotency.js index 78f13351d..c478527b7 100644 --- a/packages/base-driver/lib/express/idempotency.js +++ b/packages/base-driver/lib/express/idempotency.js @@ -1,16 +1,15 @@ import log from './logger'; import LRU from 'lru-cache'; -import { fs, util } from '@appium/support'; +import {fs, util} from '@appium/support'; import os from 'os'; import path from 'path'; -import { EventEmitter } from 'events'; - +import {EventEmitter} from 'events'; const CACHE_SIZE = 1024; const IDEMPOTENT_RESPONSES = new LRU({ max: CACHE_SIZE, updateAgeOnGet: true, - dispose (key, {response}) { + dispose(key, {response}) { if (response) { fs.rimrafSync(response); } @@ -20,9 +19,7 @@ const MONITORED_METHODS = ['POST', 'PATCH']; const IDEMPOTENCY_KEY_HEADER = 'x-idempotency-key'; process.on('exit', () => { - const resPaths = [...IDEMPOTENT_RESPONSES.values()] - .map(({response}) => response) - .filter(Boolean); + const resPaths = [...IDEMPOTENT_RESPONSES.values()].map(({response}) => response).filter(Boolean); for (const resPath of resPaths) { try { // Asynchronous calls are not supported in onExit handler @@ -31,8 +28,7 @@ process.on('exit', () => { } }); - -function cacheResponse (key, req, res) { +function cacheResponse(key, req, res) { const responseStateListener = new EventEmitter(); IDEMPOTENT_RESPONSES.set(key, { method: req.method, @@ -72,8 +68,10 @@ function cacheResponse (key, req, res) { } if (!IDEMPOTENT_RESPONSES.has(key)) { - log.info(`Could not cache the response identified by '${key}'. ` + - `Cache consistency has been damaged`); + log.info( + `Could not cache the response identified by '${key}'. ` + + `Cache consistency has been damaged` + ); return responseStateListener.emit('ready', null); } if (writeError) { @@ -82,8 +80,10 @@ function cacheResponse (key, req, res) { return responseStateListener.emit('ready', null); } if (!isResponseFullySent) { - log.info(`Could not cache the response identified by '${key}', ` + - `because it has not been completed`); + log.info( + `Could not cache the response identified by '${key}', ` + + `because it has not been completed` + ); log.info('Does the client terminate connections too early?'); IDEMPOTENT_RESPONSES.delete(key); return responseStateListener.emit('ready', null); @@ -94,7 +94,7 @@ function cacheResponse (key, req, res) { }); } -async function handleIdempotency (req, res, next) { +async function handleIdempotency(req, res, next) { const key = req.headers[IDEMPOTENCY_KEY_HEADER]; if (!key) { return next(); @@ -124,7 +124,7 @@ async function handleIdempotency (req, res, next) { } const rerouteCachedResponse = async (cachedResPath) => { - if (!await fs.exists(cachedResPath)) { + if (!(await fs.exists(cachedResPath))) { IDEMPOTENT_RESPONSES.delete(key); log.warn(`Could not read the cached response identified by key '${key}'`); log.warn('The temporary storage is not accessible anymore'); @@ -149,4 +149,4 @@ async function handleIdempotency (req, res, next) { } } -export { handleIdempotency }; +export {handleIdempotency}; diff --git a/packages/base-driver/lib/express/logger.js b/packages/base-driver/lib/express/logger.js index 2c9c5f70d..76a40b7cd 100644 --- a/packages/base-driver/lib/express/logger.js +++ b/packages/base-driver/lib/express/logger.js @@ -1,4 +1,4 @@ -import { logger } from '@appium/support'; +import {logger} from '@appium/support'; const log = logger.getLogger('HTTP'); export default log; diff --git a/packages/base-driver/lib/express/middleware.js b/packages/base-driver/lib/express/middleware.js index f2a7704f3..5b25190c6 100644 --- a/packages/base-driver/lib/express/middleware.js +++ b/packages/base-driver/lib/express/middleware.js @@ -1,13 +1,16 @@ import _ from 'lodash'; import log from './logger'; -import { errors } from '../protocol'; -import { handleIdempotency } from './idempotency'; +import {errors} from '../protocol'; +import {handleIdempotency} from './idempotency'; -function allowCrossDomain (req, res, next) { +function allowCrossDomain(req, res, next) { try { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS, DELETE'); - res.header('Access-Control-Allow-Headers', 'Cache-Control, Pragma, Origin, X-Requested-With, Content-Type, Accept, User-Agent'); + res.header( + 'Access-Control-Allow-Headers', + 'Cache-Control, Pragma, Origin, X-Requested-With, Content-Type, Accept, User-Agent' + ); // need to respond 200 to OPTIONS if ('OPTIONS' === req.method) { @@ -19,11 +22,13 @@ function allowCrossDomain (req, res, next) { next(); } -function allowCrossDomainAsyncExecute (basePath) { +function allowCrossDomainAsyncExecute(basePath) { return (req, res, next) => { // there are two paths for async responses, so cover both // https://regex101.com/r/txYiEz/1 - const receiveAsyncResponseRegExp = new RegExp(`${_.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response`); + const receiveAsyncResponseRegExp = new RegExp( + `${_.escapeRegExp(basePath)}/session/[a-f0-9-]+/(appium/)?receive_async_response` + ); if (!receiveAsyncResponseRegExp.test(req.url)) { return next(); } @@ -31,10 +36,13 @@ function allowCrossDomainAsyncExecute (basePath) { }; } -function fixPythonContentType (basePath) { +function fixPythonContentType(basePath) { return (req, res, next) => { // hack because python client library gives us wrong content-type - if (new RegExp(`^${_.escapeRegExp(basePath)}`).test(req.path) && /^Python/.test(req.headers['user-agent'])) { + if ( + new RegExp(`^${_.escapeRegExp(basePath)}`).test(req.path) && + /^Python/.test(req.headers['user-agent']) + ) { if (req.headers['content-type'] === 'application/x-www-form-urlencoded') { req.headers['content-type'] = 'application/json; charset=utf-8'; } @@ -43,14 +51,14 @@ function fixPythonContentType (basePath) { }; } -function defaultToJSONContentType (req, res, next) { +function defaultToJSONContentType(req, res, next) { if (!req.headers['content-type']) { req.headers['content-type'] = 'application/json; charset=utf-8'; } next(); } -function catchAllHandler (err, req, res, next) { +function catchAllHandler(err, req, res, next) { if (res.headersSent) { return next(err); } @@ -58,36 +66,40 @@ function catchAllHandler (err, req, res, next) { log.error(`Uncaught error: ${err.message}`); log.error('Sending generic error response'); const error = errors.UnknownError; - res.status(error.w3cStatus()).json(patchWithSessionId(req, { - status: error.code(), - value: { - error: error.error(), - message: `An unknown server-side error occurred while processing the command: ${err.message}`, - stacktrace: err.stack, - } - })); + res.status(error.w3cStatus()).json( + patchWithSessionId(req, { + status: error.code(), + value: { + error: error.error(), + message: `An unknown server-side error occurred while processing the command: ${err.message}`, + stacktrace: err.stack, + }, + }) + ); log.error(err); } -function catch404Handler (req, res) { +function catch404Handler(req, res) { log.debug(`No route found for ${req.url}`); const error = errors.UnknownCommandError; - res.status(error.w3cStatus()).json(patchWithSessionId(req, { - status: error.code(), - value: { - error: error.error(), - message: 'The requested resource could not be found, or a request was ' + - 'received using an HTTP method that is not supported by the mapped ' + - 'resource', - stacktrace: '', - } - })); + res.status(error.w3cStatus()).json( + patchWithSessionId(req, { + status: error.code(), + value: { + error: error.error(), + message: + 'The requested resource could not be found, or a request was ' + + 'received using an HTTP method that is not supported by the mapped ' + + 'resource', + stacktrace: '', + }, + }) + ); } - const SESSION_ID_PATTERN = /\/session\/([^/]+)/; -function patchWithSessionId (req, body) { +function patchWithSessionId(req, body) { const match = SESSION_ID_PATTERN.exec(req.url); if (match) { body.sessionId = match[1]; @@ -96,7 +108,11 @@ function patchWithSessionId (req, body) { } export { - allowCrossDomain, fixPythonContentType, defaultToJSONContentType, - catchAllHandler, allowCrossDomainAsyncExecute, handleIdempotency, + allowCrossDomain, + fixPythonContentType, + defaultToJSONContentType, + catchAllHandler, + allowCrossDomainAsyncExecute, + handleIdempotency, catch404Handler, }; diff --git a/packages/base-driver/lib/express/server.js b/packages/base-driver/lib/express/server.js index a2dd0fdb5..37d4e02a7 100644 --- a/packages/base-driver/lib/express/server.js +++ b/packages/base-driver/lib/express/server.js @@ -6,27 +6,31 @@ import favicon from 'serve-favicon'; import bodyParser from 'body-parser'; import methodOverride from 'method-override'; import log from './logger'; -import { startLogFormatter, endLogFormatter } from './express-logging'; +import {startLogFormatter, endLogFormatter} from './express-logging'; import { - allowCrossDomain, fixPythonContentType, defaultToJSONContentType, - catchAllHandler, allowCrossDomainAsyncExecute, handleIdempotency, + allowCrossDomain, + fixPythonContentType, + defaultToJSONContentType, + catchAllHandler, + allowCrossDomainAsyncExecute, + handleIdempotency, catch404Handler, } from './middleware'; -import { guineaPig, guineaPigScrollable, guineaPigAppBanner, welcome, STATIC_DIR } from './static'; -import { produceError, produceCrash } from './crash'; +import {guineaPig, guineaPigScrollable, guineaPigAppBanner, welcome, STATIC_DIR} from './static'; +import {produceError, produceCrash} from './crash'; import { - addWebSocketHandler, removeWebSocketHandler, removeAllWebSocketHandlers, - getWebSocketHandlers + addWebSocketHandler, + removeWebSocketHandler, + removeAllWebSocketHandlers, + getWebSocketHandlers, } from './websocket'; import B from 'bluebird'; -import { DEFAULT_BASE_PATH } from '../constants'; -import { EventEmitter } from 'events'; - +import {DEFAULT_BASE_PATH} from '../constants'; +import {EventEmitter} from 'events'; const KEEP_ALIVE_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes - -async function server (opts = {}) { +async function server(opts = {}) { const { routeConfiguringFunction, port, @@ -49,7 +53,13 @@ async function server (opts = {}) { // try/catch so any errors can be passed to reject. try { configureHttp({httpServer, reject, keepAliveTimeout}); - configureServer({app, addRoutes: routeConfiguringFunction, allowCors, basePath, extraMethodMap}); + configureServer({ + app, + addRoutes: routeConfiguringFunction, + allowCors, + basePath, + extraMethodMap, + }); // allow extensions to update the app and http server objects for (const updater of serverUpdaters) { await updater(app, httpServer); @@ -66,10 +76,9 @@ async function server (opts = {}) { reject(err); } }); - } -function configureServer ({ +function configureServer({ app, addRoutes, allowCors = true, @@ -116,7 +125,7 @@ function configureServer ({ app.all('/test/guinea-pig-app-banner', guineaPigAppBanner); } -function configureHttp ({httpServer, reject, keepAliveTimeout}) { +function configureHttp({httpServer, reject, keepAliveTimeout}) { const serverState = { notifier: new EventEmitter(), closed: false, @@ -129,28 +138,32 @@ function configureHttp ({httpServer, reject, keepAliveTimeout}) { // http.Server.close() only stops new connections, but we need to wait until // all connections are closed and the `close` event is emitted const close = httpServer.close.bind(httpServer); - httpServer.close = async () => await new B((resolve, reject) => { - // https://github.com/nodejs/node-v0.x-archive/issues/9066#issuecomment-124210576 - serverState.closed = true; - serverState.notifier.emit('shutdown'); - log.info('Waiting until the server is closed'); - httpServer.on('close', () => { - log.info('Received server close event'); - resolve(); + httpServer.close = async () => + await new B((resolve, reject) => { + // https://github.com/nodejs/node-v0.x-archive/issues/9066#issuecomment-124210576 + serverState.closed = true; + serverState.notifier.emit('shutdown'); + log.info('Waiting until the server is closed'); + httpServer.on('close', () => { + log.info('Received server close event'); + resolve(); + }); + close((err) => { + if (err) reject(err); // eslint-disable-line curly + }); }); - close((err) => { - if (err) reject(err); // eslint-disable-line curly - }); - }); httpServer.on('error', (err) => { if (err.code === 'EADDRNOTAVAIL') { - log.error('Could not start REST http interface listener. ' + - 'Requested address is not available.'); + log.error( + 'Could not start REST http interface listener. ' + 'Requested address is not available.' + ); } else { - log.error('Could not start REST http interface listener. The requested ' + - 'port may already be in use. Please make sure there is no ' + - 'other instance of this server running already.'); + log.error( + 'Could not start REST http interface listener. The requested ' + + 'port may already be in use. Please make sure there is no ' + + 'other instance of this server running already.' + ); } reject(err); }); @@ -159,7 +172,7 @@ function configureHttp ({httpServer, reject, keepAliveTimeout}) { socket.setTimeout(keepAliveTimeout); socket.on('error', reject); - function destroy () { + function destroy() { socket.destroy(); } socket._openReqCount = 0; @@ -179,7 +192,7 @@ function configureHttp ({httpServer, reject, keepAliveTimeout}) { }); } -async function startServer ({httpServer, port, hostname, keepAliveTimeout}) { +async function startServer({httpServer, port, hostname, keepAliveTimeout}) { const serverArgs = [port]; if (hostname) { // If the hostname is omitted, the server will accept @@ -193,7 +206,7 @@ async function startServer ({httpServer, port, hostname, keepAliveTimeout}) { await startPromise; } -function normalizeBasePath (basePath) { +function normalizeBasePath(basePath) { if (!_.isString(basePath)) { throw new Error(`Invalid path prefix ${basePath}`); } @@ -211,4 +224,4 @@ function normalizeBasePath (basePath) { return basePath; } -export { server, configureServer, normalizeBasePath }; +export {server, configureServer, normalizeBasePath}; diff --git a/packages/base-driver/lib/express/static.js b/packages/base-driver/lib/express/static.js index 76d8a7792..7f2d7cd4f 100644 --- a/packages/base-driver/lib/express/static.js +++ b/packages/base-driver/lib/express/static.js @@ -1,10 +1,9 @@ import path from 'path'; import log from './logger'; import _ from 'lodash'; -import { fs } from '@appium/support'; +import {fs} from '@appium/support'; import B from 'bluebird'; - let STATIC_DIR = path.resolve(__dirname, '..', '..', '..', 'static'); if (_.isNull(path.resolve(__dirname).match(/build[/\\]lib[/\\]express$/))) { // in some contexts we are not in the build directory, @@ -12,14 +11,14 @@ if (_.isNull(path.resolve(__dirname).match(/build[/\\]lib[/\\]express$/))) { STATIC_DIR = path.resolve(__dirname, '..', '..', 'static'); } -async function guineaPigTemplate (req, res, page) { +async function guineaPigTemplate(req, res, page) { const delay = parseInt(req.params.delay || req.query.delay || 0, 10); const throwError = req.params.throwError || req.query.throwError || ''; let params = { throwError, serverTime: new Date(), userAgent: req.headers['user-agent'], - comment: 'None' + comment: 'None', }; if (req.method === 'POST') { params.comment = req.body.comments || params.comment; @@ -34,7 +33,7 @@ async function guineaPigTemplate (req, res, page) { res.cookie('guineacookie2', 'cookié2', {path: '/'}); res.cookie('guineacookie3', 'cant access this', { domain: '.blargimarg.com', - path: '/' + path: '/', }); res.send((await getTemplate(page))(params)); } @@ -42,36 +41,36 @@ async function guineaPigTemplate (req, res, page) { /* * Dynamic page mapped to /test/guinea-pig */ -async function guineaPig (req, res) { +async function guineaPig(req, res) { return await guineaPigTemplate(req, res, 'guinea-pig.html'); } /* * Dynamic page mapped to /test/guinea-pig-scrollable */ -async function guineaPigScrollable (req, res) { +async function guineaPigScrollable(req, res) { return await guineaPigTemplate(req, res, 'guinea-pig-scrollable.html'); } /* * Dynamic page mapped to /test/guinea-pig-app-banner */ -async function guineaPigAppBanner (req, res) { +async function guineaPigAppBanner(req, res) { return await guineaPigTemplate(req, res, 'guinea-pig-app-banner.html'); } /* * Dynamic page mapped to /welcome */ -async function welcome (req, res) { - let params = {message: 'Let\'s browse!'}; +async function welcome(req, res) { + let params = {message: "Let's browse!"}; log.debug(`Sending welcome response with params: ${JSON.stringify(params)}`); res.send((await getTemplate('welcome.html'))(params)); } -async function getTemplate (templateName) { +async function getTemplate(templateName) { let content = await fs.readFile(path.resolve(STATIC_DIR, 'test', templateName)); return _.template(content.toString()); } -export { guineaPig, guineaPigScrollable, guineaPigAppBanner, welcome, STATIC_DIR }; +export {guineaPig, guineaPigScrollable, guineaPigAppBanner, welcome, STATIC_DIR}; diff --git a/packages/base-driver/lib/express/websocket.js b/packages/base-driver/lib/express/websocket.js index 1602ef347..018520b18 100644 --- a/packages/base-driver/lib/express/websocket.js +++ b/packages/base-driver/lib/express/websocket.js @@ -1,10 +1,9 @@ import _ from 'lodash'; -import { URL } from 'url'; +import {URL} from 'url'; import B from 'bluebird'; const DEFAULT_WS_PATHNAME_PREFIX = '/ws'; - /** * Adds websocket handler to express server instance. * It is expected this function is called in Express @@ -18,14 +17,15 @@ const DEFAULT_WS_PATHNAME_PREFIX = '/ws'; * https://github.com/websockets/ws/pull/885 for more details * on how to configure the handler properly. */ -async function addWebSocketHandler (handlerPathname, handlerServer) { // eslint-disable-line require-await +// eslint-disable-next-line require-await +async function addWebSocketHandler(handlerPathname, handlerServer) { if (_.isUndefined(this.webSocketsMapping)) { this.webSocketsMapping = {}; // https://github.com/websockets/ws/pull/885 this.on('upgrade', (request, socket, head) => { let currentPathname; try { - currentPathname = (new URL(request.url)).pathname; + currentPathname = new URL(request.url).pathname; } catch (ign) { currentPathname = request.url; } @@ -54,7 +54,8 @@ async function addWebSocketHandler (handlerPathname, handlerServer) { // eslint- * @returns {Object} pathnames to websocket server isntances mapping * matching the search criteria or an empty object otherwise. */ -async function getWebSocketHandlers (keysFilter = null) { // eslint-disable-line require-await +// eslint-disable-next-line require-await +async function getWebSocketHandlers(keysFilter = null) { if (_.isEmpty(this.webSocketsMapping)) { return {}; } @@ -77,7 +78,8 @@ async function getWebSocketHandlers (keysFilter = null) { // eslint-disable-line * @param {string} handlerPathname - Websocket endpoint path. * @returns {boolean} true if the handlerPathname was found and deleted */ -async function removeWebSocketHandler (handlerPathname) { // eslint-disable-line require-await +// eslint-disable-next-line require-await +async function removeWebSocketHandler(handlerPathname) { const wsServer = this.webSocketsMapping?.[handlerPathname]; if (!wsServer) { return false; @@ -85,7 +87,7 @@ async function removeWebSocketHandler (handlerPathname) { // eslint-disable-line try { wsServer.close(); - for (const client of (wsServer.clients || [])) { + for (const client of wsServer.clients || []) { client.terminate(); } return true; @@ -104,7 +106,7 @@ async function removeWebSocketHandler (handlerPathname) { // eslint-disable-line * * @returns {boolean} true if at least one handler has been deleted */ -async function removeAllWebSocketHandlers () { +async function removeAllWebSocketHandlers() { if (_.isEmpty(this.webSocketsMapping)) { return false; } @@ -117,6 +119,9 @@ async function removeAllWebSocketHandlers () { } export { - addWebSocketHandler, removeWebSocketHandler, removeAllWebSocketHandlers, - getWebSocketHandlers, DEFAULT_WS_PATHNAME_PREFIX, + addWebSocketHandler, + removeWebSocketHandler, + removeAllWebSocketHandlers, + getWebSocketHandlers, + DEFAULT_WS_PATHNAME_PREFIX, }; diff --git a/packages/base-driver/lib/helpers/capabilities.js b/packages/base-driver/lib/helpers/capabilities.js index ec72499cd..003320be8 100644 --- a/packages/base-driver/lib/helpers/capabilities.js +++ b/packages/base-driver/lib/helpers/capabilities.js @@ -2,13 +2,15 @@ import _ from 'lodash'; -function isW3cCaps (caps) { +function isW3cCaps(caps) { if (!_.isPlainObject(caps)) { return false; } - const isFirstMatchValid = () => _.isArray(caps.firstMatch) - && !_.isEmpty(caps.firstMatch) && _.every(caps.firstMatch, _.isPlainObject); + const isFirstMatchValid = () => + _.isArray(caps.firstMatch) && + !_.isEmpty(caps.firstMatch) && + _.every(caps.firstMatch, _.isPlainObject); const isAlwaysMatchValid = () => _.isPlainObject(caps.alwaysMatch); if (_.has(caps, 'firstMatch') && _.has(caps, 'alwaysMatch')) { return isFirstMatchValid() && isAlwaysMatchValid(); @@ -29,21 +31,19 @@ function isW3cCaps (caps) { * @param {AppiumLogger} log * @returns {Capabilities} */ -function fixCaps (originalCaps, desiredCapConstraints, log) { +function fixCaps(originalCaps, desiredCapConstraints, log) { let caps = _.clone(originalCaps); // boolean capabilities can be passed in as strings 'false' and 'true' // which we want to translate into boolean values - let booleanCaps = _.keys( - _.pickBy(desiredCapConstraints, (k) => k.isBoolean === true), - ); + let booleanCaps = _.keys(_.pickBy(desiredCapConstraints, (k) => k.isBoolean === true)); for (let cap of booleanCaps) { let value = originalCaps[cap]; if (_.isString(value)) { value = value.toLowerCase(); if (value === 'true' || value === 'false') { log.warn( - `Capability '${cap}' changed from string to boolean. This may cause unexpected behavior`, + `Capability '${cap}' changed from string to boolean. This may cause unexpected behavior` ); caps[cap] = value === 'true'; } @@ -51,9 +51,7 @@ function fixCaps (originalCaps, desiredCapConstraints, log) { } // int capabilities are often sent in as strings by frameworks - let intCaps = _.keys( - _.pickBy(desiredCapConstraints, (k) => k.isNumber === true), - ); + let intCaps = _.keys(_.pickBy(desiredCapConstraints, (k) => k.isNumber === true)); for (let cap of intCaps) { let value = originalCaps[cap]; if (_.isString(value)) { @@ -63,7 +61,7 @@ function fixCaps (originalCaps, desiredCapConstraints, log) { newValue = parseFloat(value); } log.warn( - `Capability '${cap}' changed from string ('${value}') to integer (${newValue}). This may cause unexpected behavior`, + `Capability '${cap}' changed from string ('${value}') to integer (${newValue}). This may cause unexpected behavior` ); caps[cap] = newValue; } @@ -72,10 +70,7 @@ function fixCaps (originalCaps, desiredCapConstraints, log) { return caps; } -export { - isW3cCaps, - fixCaps -}; +export {isW3cCaps, fixCaps}; /** * @typedef {import('@appium/types').Capabilities} Capabilities diff --git a/packages/base-driver/lib/index.js b/packages/base-driver/lib/index.js index 15f4a589b..122416b2d 100644 --- a/packages/base-driver/lib/index.js +++ b/packages/base-driver/lib/index.js @@ -7,62 +7,79 @@ B.config({ }); // BaseDriver exports -import { BaseDriver } from './basedriver/driver'; -export { DriverCore } from './basedriver/core'; -import { DeviceSettings } from './basedriver/device-settings'; +import {BaseDriver} from './basedriver/driver'; +export {DriverCore} from './basedriver/core'; +import {DeviceSettings} from './basedriver/device-settings'; -export { BaseDriver, DeviceSettings }; +export {BaseDriver, DeviceSettings}; export default BaseDriver; - // MJSONWP exports import * as protocol from './protocol'; -import { - DEFAULT_BASE_PATH, PROTOCOLS -} from './constants'; +import {DEFAULT_BASE_PATH, PROTOCOLS} from './constants'; const { - routeConfiguringFunction, errors, isErrorType, - errorFromMJSONWPStatusCode, errorFromW3CJsonCode, ALL_COMMANDS, METHOD_MAP, - routeToCommandName, NO_SESSION_ID_COMMANDS, isSessionCommand, - determineProtocol, CREATE_SESSION_COMMAND, - DELETE_SESSION_COMMAND, GET_STATUS_COMMAND, + routeConfiguringFunction, + errors, + isErrorType, + errorFromMJSONWPStatusCode, + errorFromW3CJsonCode, + ALL_COMMANDS, + METHOD_MAP, + routeToCommandName, + NO_SESSION_ID_COMMANDS, + isSessionCommand, + determineProtocol, + CREATE_SESSION_COMMAND, + DELETE_SESSION_COMMAND, + GET_STATUS_COMMAND, } = protocol; export { - routeConfiguringFunction, errors, isErrorType, PROTOCOLS, - errorFromMJSONWPStatusCode, errorFromW3CJsonCode, determineProtocol, - errorFromMJSONWPStatusCode as errorFromCode, ALL_COMMANDS, METHOD_MAP, - routeToCommandName, NO_SESSION_ID_COMMANDS, isSessionCommand, - DEFAULT_BASE_PATH, CREATE_SESSION_COMMAND, - DELETE_SESSION_COMMAND, GET_STATUS_COMMAND, + routeConfiguringFunction, + errors, + isErrorType, + PROTOCOLS, + errorFromMJSONWPStatusCode, + errorFromW3CJsonCode, + determineProtocol, + errorFromMJSONWPStatusCode as errorFromCode, + ALL_COMMANDS, + METHOD_MAP, + routeToCommandName, + NO_SESSION_ID_COMMANDS, + isSessionCommand, + DEFAULT_BASE_PATH, + CREATE_SESSION_COMMAND, + DELETE_SESSION_COMMAND, + GET_STATUS_COMMAND, }; // Express exports import * as staticIndex from './express/static'; -const { STATIC_DIR } = staticIndex; -export { STATIC_DIR }; +const {STATIC_DIR} = staticIndex; +export {STATIC_DIR}; import * as serverIndex from './express/server'; -const { server, normalizeBasePath } = serverIndex; -export { server, normalizeBasePath }; +const {server, normalizeBasePath} = serverIndex; +export {server, normalizeBasePath}; // jsonwp-proxy exports import * as proxyIndex from './jsonwp-proxy/proxy'; -const { JWProxy } = proxyIndex; -export { JWProxy }; +const {JWProxy} = proxyIndex; +export {JWProxy}; // jsonwp-status exports import * as statusIndex from './jsonwp-status/status'; -const { codes: statusCodes, getSummaryByCode } = statusIndex; -export { statusCodes, getSummaryByCode }; +const {codes: statusCodes, getSummaryByCode} = statusIndex; +export {statusCodes, getSummaryByCode}; // W3C capabilities parser import * as caps from './basedriver/capabilities'; -const { processCapabilities, isStandardCap, validateCaps } = caps; -export { processCapabilities, isStandardCap, validateCaps }; +const {processCapabilities, isStandardCap, validateCaps} = caps; +export {processCapabilities, isStandardCap, validateCaps}; // Web socket helpers import * as ws from './express/websocket'; -const { DEFAULT_WS_PATHNAME_PREFIX } = ws; -export { DEFAULT_WS_PATHNAME_PREFIX }; +const {DEFAULT_WS_PATHNAME_PREFIX} = ws; +export {DEFAULT_WS_PATHNAME_PREFIX}; diff --git a/packages/base-driver/lib/jsonwp-proxy/protocol-converter.js b/packages/base-driver/lib/jsonwp-proxy/protocol-converter.js index 42c3a032c..379f04563 100644 --- a/packages/base-driver/lib/jsonwp-proxy/protocol-converter.js +++ b/packages/base-driver/lib/jsonwp-proxy/protocol-converter.js @@ -1,33 +1,29 @@ import _ from 'lodash'; -import { logger, util } from '@appium/support'; -import { duplicateKeys } from '../basedriver/helpers'; -import { - MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS -} from '../constants'; +import {logger, util} from '@appium/support'; +import {duplicateKeys} from '../basedriver/helpers'; +import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS} from '../constants'; export const COMMAND_URLS_CONFLICTS = [ { commandNames: ['execute', 'executeAsync'], - jsonwpConverter: (url) => url.replace(/\/execute.*/, - url.includes('async') ? '/execute_async' : '/execute'), - w3cConverter: (url) => url.replace(/\/execute.*/, - url.includes('async') ? '/execute/async' : '/execute/sync'), + jsonwpConverter: (url) => + url.replace(/\/execute.*/, url.includes('async') ? '/execute_async' : '/execute'), + w3cConverter: (url) => + url.replace(/\/execute.*/, url.includes('async') ? '/execute/async' : '/execute/sync'), }, { commandNames: ['getElementScreenshot'], - jsonwpConverter: (url) => url.replace(/\/element\/([^/]+)\/screenshot$/, - '/screenshot/$1'), - w3cConverter: (url) => url.replace(/\/screenshot\/([^/]+)/, - '/element/$1/screenshot'), + jsonwpConverter: (url) => url.replace(/\/element\/([^/]+)\/screenshot$/, '/screenshot/$1'), + w3cConverter: (url) => url.replace(/\/screenshot\/([^/]+)/, '/element/$1/screenshot'), }, { commandNames: ['getWindowHandles', 'getWindowHandle'], - jsonwpConverter (url) { + jsonwpConverter(url) { return /\/window$/.test(url) ? url.replace(/\/window$/, '/window_handle') : url.replace(/\/window\/handle(s?)$/, '/window_handle$1'); }, - w3cConverter (url) { + w3cConverter(url) { return /\/window_handle$/.test(url) ? url.replace(/\/window_handle$/, '/window') : url.replace(/\/window_handles$/, '/window/handles'); @@ -40,29 +36,28 @@ export const COMMAND_URLS_CONFLICTS = [ const jsonwpUrl = w3cUrl.replace(w3cPropertyRegex, '/element/$1/attribute/$2'); return jsonwpUrl; }, - w3cConverter: (jsonwpUrl) => jsonwpUrl // Don't convert JSONWP URL to W3C. W3C accepts /attribute and /property - } + w3cConverter: (jsonwpUrl) => jsonwpUrl, // Don't convert JSONWP URL to W3C. W3C accepts /attribute and /property + }, ]; const {MJSONWP, W3C} = PROTOCOLS; const DEFAULT_LOG = logger.getLogger('Protocol Converter'); - class ProtocolConverter { - constructor (proxyFunc, log = null) { + constructor(proxyFunc, log = null) { this.proxyFunc = proxyFunc; this._downstreamProtocol = null; this._log = log; } - get log () { + get log() { return this._log ?? DEFAULT_LOG; } - set downstreamProtocol (value) { + set downstreamProtocol(value) { this._downstreamProtocol = value; } - get downstreamProtocol () { + get downstreamProtocol() { return this._downstreamProtocol; } @@ -74,25 +69,29 @@ class ProtocolConverter { * @param {Object} body Request body * @return {Array} Array of W3C + MJSONWP compatible timeout objects */ - getTimeoutRequestObjects (body) { + getTimeoutRequestObjects(body) { if (this.downstreamProtocol === W3C && _.has(body, 'ms') && _.has(body, 'type')) { - const typeToW3C = (x) => x === 'page load' ? 'pageLoad' : x; - return [{ - [typeToW3C(body.type)]: body.ms, - }]; + const typeToW3C = (x) => (x === 'page load' ? 'pageLoad' : x); + return [ + { + [typeToW3C(body.type)]: body.ms, + }, + ]; } if (this.downstreamProtocol === MJSONWP && (!_.has(body, 'ms') || !_.has(body, 'type'))) { - const typeToJSONWP = (x) => x === 'pageLoad' ? 'page load' : x; - return _.toPairs(body) - // Only transform the entry if ms value is a valid positive float number - .filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`)) - .map(function (pair) { - return { - type: typeToJSONWP(pair[0]), - ms: pair[1], - }; - }); + const typeToJSONWP = (x) => (x === 'pageLoad' ? 'page load' : x); + return ( + _.toPairs(body) + // Only transform the entry if ms value is a valid positive float number + .filter((pair) => /^\d+(?:[.,]\d*?)?$/.test(`${pair[1]}`)) + .map(function (pair) { + return { + type: typeToJSONWP(pair[0]), + ms: pair[1], + }; + }) + ); } return [body]; @@ -104,11 +103,15 @@ class ProtocolConverter { * @param {String} method Endpoint method * @param {Object} body Request body */ - async proxySetTimeouts (url, method, body) { + async proxySetTimeouts(url, method, body) { let response, resBody; const timeoutRequestObjects = this.getTimeoutRequestObjects(body); - this.log.debug(`Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}`); + this.log.debug( + `Will send the following request bodies to /timeouts: ${JSON.stringify( + timeoutRequestObjects + )}` + ); for (const timeoutObj of timeoutRequestObjects) { [response, resBody] = await this.proxyFunc(url, method, timeoutObj); @@ -127,7 +130,7 @@ class ProtocolConverter { return [response, resBody]; } - async proxySetWindow (url, method, body) { + async proxySetWindow(url, method, body) { const bodyObj = util.safeJsonParse(body); if (_.isPlainObject(bodyObj)) { if (this.downstreamProtocol === W3C && _.has(bodyObj, 'name') && !_.has(bodyObj, 'handle')) { @@ -137,7 +140,11 @@ class ProtocolConverter { handle: bodyObj.name, }); } - if (this.downstreamProtocol === MJSONWP && _.has(bodyObj, 'handle') && !_.has(bodyObj, 'name')) { + if ( + this.downstreamProtocol === MJSONWP && + _.has(bodyObj, 'handle') && + !_.has(bodyObj, 'name') + ) { this.log.debug(`Copied 'handle' value '${bodyObj.handle}' to 'name' as per JSONWP spec`); return await this.proxyFunc(url, method, { ...bodyObj, @@ -149,48 +156,54 @@ class ProtocolConverter { return await this.proxyFunc(url, method, body); } - async proxySetValue (url, method, body) { + async proxySetValue(url, method, body) { const bodyObj = util.safeJsonParse(body); if (_.isPlainObject(bodyObj) && (util.hasValue(bodyObj.text) || util.hasValue(bodyObj.value))) { let {text, value} = bodyObj; if (util.hasValue(text) && !util.hasValue(value)) { - value = _.isString(text) - ? [...text] - : (_.isArray(text) ? text : []); - this.log.debug(`Added 'value' property ${JSON.stringify(value)} to 'setValue' request body`); + value = _.isString(text) ? [...text] : _.isArray(text) ? text : []; + this.log.debug( + `Added 'value' property ${JSON.stringify(value)} to 'setValue' request body` + ); } else if (!util.hasValue(text) && util.hasValue(value)) { - text = _.isArray(value) - ? value.join('') - : (_.isString(value) ? value : ''); + text = _.isArray(value) ? value.join('') : _.isString(value) ? value : ''; this.log.debug(`Added 'text' property ${JSON.stringify(text)} to 'setValue' request body`); } - return await this.proxyFunc(url, method, Object.assign({}, bodyObj, { - text, - value, - })); + return await this.proxyFunc( + url, + method, + Object.assign({}, bodyObj, { + text, + value, + }) + ); } return await this.proxyFunc(url, method, body); } - async proxySetFrame (url, method, body) { + async proxySetFrame(url, method, body) { const bodyObj = util.safeJsonParse(body); return _.has(bodyObj, 'id') && _.isPlainObject(bodyObj.id) ? await this.proxyFunc(url, method, { - ...bodyObj, - id: duplicateKeys(bodyObj.id, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY), - }) + ...bodyObj, + id: duplicateKeys(bodyObj.id, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY), + }) : await this.proxyFunc(url, method, body); } - async proxyPerformActions (url, method, body) { + async proxyPerformActions(url, method, body) { const bodyObj = util.safeJsonParse(body); return _.isPlainObject(bodyObj) - ? await this.proxyFunc(url, method, duplicateKeys(bodyObj, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY)) + ? await this.proxyFunc( + url, + method, + duplicateKeys(bodyObj, MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY) + ) : await this.proxyFunc(url, method, body); } - async proxyReleaseActions (url, method) { + async proxyReleaseActions(url, method) { return await this.proxyFunc(url, method); } @@ -204,7 +217,7 @@ class ProtocolConverter { * @param {?string|object} body * @returns The proxyfying result as [response, responseBody] tuple */ - async convertAndProxy (commandName, url, method, body) { + async convertAndProxy(commandName, url, method, body) { if (!this.downstreamProtocol) { return await this.proxyFunc(url, method, body); } @@ -233,16 +246,19 @@ class ProtocolConverter { continue; } - const rewrittenUrl = this.downstreamProtocol === MJSONWP - ? jsonwpConverter(url) - : w3cConverter(url); + const rewrittenUrl = + this.downstreamProtocol === MJSONWP ? jsonwpConverter(url) : w3cConverter(url); if (rewrittenUrl === url) { - this.log.debug(`Did not know how to rewrite the original URL '${url}' ` + - `for ${this.downstreamProtocol} protocol`); + this.log.debug( + `Did not know how to rewrite the original URL '${url}' ` + + `for ${this.downstreamProtocol} protocol` + ); break; } - this.log.info(`Rewrote the original URL '${url}' to '${rewrittenUrl}' ` + - `for ${this.downstreamProtocol} protocol`); + this.log.info( + `Rewrote the original URL '${url}' to '${rewrittenUrl}' ` + + `for ${this.downstreamProtocol} protocol` + ); return await this.proxyFunc(rewrittenUrl, method, body); } diff --git a/packages/base-driver/lib/jsonwp-proxy/proxy.js b/packages/base-driver/lib/jsonwp-proxy/proxy.js index bed563643..0e6a1e9e5 100644 --- a/packages/base-driver/lib/jsonwp-proxy/proxy.js +++ b/packages/base-driver/lib/jsonwp-proxy/proxy.js @@ -1,29 +1,29 @@ import _ from 'lodash'; -import { logger, util } from '@appium/support'; +import {logger, util} from '@appium/support'; import axios from 'axios'; -import { getSummaryByCode } from '../jsonwp-status/status'; +import {getSummaryByCode} from '../jsonwp-status/status'; import { - errors, isErrorType, errorFromMJSONWPStatusCode, errorFromW3CJsonCode, + errors, + isErrorType, + errorFromMJSONWPStatusCode, + errorFromW3CJsonCode, getResponseForW3CError, } from '../protocol/errors'; -import { routeToCommandName } from '../protocol'; -import { MAX_LOG_BODY_LENGTH, DEFAULT_BASE_PATH, PROTOCOLS } from '../constants'; +import {routeToCommandName} from '../protocol'; +import {MAX_LOG_BODY_LENGTH, DEFAULT_BASE_PATH, PROTOCOLS} from '../constants'; import ProtocolConverter from './protocol-converter'; -import { formatResponseValue, formatStatus } from '../protocol/helpers'; +import {formatResponseValue, formatStatus} from '../protocol/helpers'; import http from 'http'; import https from 'https'; const DEFAULT_LOG = logger.getLogger('WD Proxy'); const DEFAULT_REQUEST_TIMEOUT = 240000; -const COMPACT_ERROR_PATTERNS = [ - /\bECONNREFUSED\b/, - /socket hang up/, -]; +const COMPACT_ERROR_PATTERNS = [/\bECONNREFUSED\b/, /socket hang up/]; const {MJSONWP, W3C} = PROTOCOLS; class JWProxy { - constructor (opts = {}) { + constructor(opts = {}) { _.defaults(this, opts, { scheme: 'http', server: 'localhost', @@ -47,7 +47,7 @@ class JWProxy { this._log = opts.log; } - get log () { + get log() { return this._log ?? DEFAULT_LOG; } @@ -60,7 +60,7 @@ class JWProxy { * @param {AxiosRequestConfig} requestConfig * @returns {AxiosResponse} */ - async request (requestConfig) { + async request(requestConfig) { const reqPromise = axios(requestConfig); this._activeRequests.push(reqPromise); try { @@ -70,28 +70,28 @@ class JWProxy { } } - getActiveRequestsCount () { + getActiveRequestsCount() { return this._activeRequests.length; } - cancelActiveRequests () { + cancelActiveRequests() { this._activeRequests = []; } - endpointRequiresSessionId (endpoint) { + endpointRequiresSessionId(endpoint) { return !_.includes(['/session', '/sessions', '/status'], endpoint); } - set downstreamProtocol (value) { + set downstreamProtocol(value) { this._downstreamProtocol = value; this.protocolConverter.downstreamProtocol = value; } - get downstreamProtocol () { + get downstreamProtocol() { return this._downstreamProtocol; } - getUrlForProxy (url) { + getUrlForProxy(url) { if (url === '') { url = '/'; } @@ -99,12 +99,12 @@ class JWProxy { const endpointRe = '(/(session|status))'; let remainingUrl = ''; if (/^http/.test(url)) { - const first = (new RegExp(`(https?://.+)${endpointRe}`)).exec(url); + const first = new RegExp(`(https?://.+)${endpointRe}`).exec(url); if (!first) { throw new Error('Got a complete url but could not extract JWP endpoint'); } remainingUrl = url.replace(first[1], ''); - } else if ((new RegExp('^/')).test(url)) { + } else if (new RegExp('^/').test(url)) { remainingUrl = url; } else { throw new Error(`Did not know what to do with url '${url}'`); @@ -115,7 +115,7 @@ class JWProxy { remainingUrl = stripPrefixRe.exec(remainingUrl)[1]; } - if (!(new RegExp(endpointRe)).test(remainingUrl)) { + if (!new RegExp(endpointRe).test(remainingUrl)) { remainingUrl = `/session/${this.sessionId}${remainingUrl}`; } @@ -139,12 +139,13 @@ class JWProxy { return proxyBase + remainingUrl; } - async proxy (url, method, body = null) { + async proxy(url, method, body = null) { method = method.toUpperCase(); const newUrl = this.getUrlForProxy(url); - const truncateBody = (content) => _.truncate( - _.isString(content) ? content : JSON.stringify(content), - { length: MAX_LOG_BODY_LENGTH }); + const truncateBody = (content) => + _.truncate(_.isString(content) ? content : JSON.stringify(content), { + length: MAX_LOG_BODY_LENGTH, + }); const reqOpts = { url: newUrl, method, @@ -171,14 +172,16 @@ class JWProxy { } } - this.log.debug(`Proxying [${method} ${url || '/'}] to [${method} ${newUrl}] ` + - (reqOpts.data ? `with body: ${truncateBody(reqOpts.data)}` : 'with no body')); + this.log.debug( + `Proxying [${method} ${url || '/'}] to [${method} ${newUrl}] ` + + (reqOpts.data ? `with body: ${truncateBody(reqOpts.data)}` : 'with no body') + ); const throwProxyError = (error) => { const err = new Error(`The request to ${url} has failed`); err.response = { data: error, - status: 500 + status: 500, }; throw err; }; @@ -216,9 +219,11 @@ class JWProxy { if (util.hasValue(e.response)) { if (!isResponseLogged) { const error = truncateBody(e.response.data); - this.log.info(util.hasValue(e.response.status) - ? `Got response with status ${e.response.status}: ${error}` - : `Got response with unknown status: ${error}`); + this.log.info( + util.hasValue(e.response.status) + ? `Got response with status ${e.response.status}: ${error}` + : `Got response with unknown status: ${error}` + ); } } else { proxyErrorMsg = `Could not proxy command to the remote server. Original error: ${e.message}`; @@ -232,7 +237,7 @@ class JWProxy { } } - getProtocolFromResBody (resObj) { + getProtocolFromResBody(resObj) { if (_.isInteger(resObj.status)) { return MJSONWP; } @@ -241,14 +246,16 @@ class JWProxy { } } - requestToCommandName (url, method) { + requestToCommandName(url, method) { const extractCommandName = (pattern) => { const pathMatch = pattern.exec(url); return pathMatch ? routeToCommandName(pathMatch[1], method, this.reqBasePath) : null; }; let commandName = routeToCommandName(url, method, this.reqBasePath); if (!commandName && _.includes(url, `${this.reqBasePath}/session/`)) { - commandName = extractCommandName(new RegExp(`${_.escapeRegExp(this.reqBasePath)}/session/[^/]+(.+)`)); + commandName = extractCommandName( + new RegExp(`${_.escapeRegExp(this.reqBasePath)}/session/[^/]+(.+)`) + ); } if (!commandName && _.includes(url, this.reqBasePath)) { commandName = extractCommandName(new RegExp(`${_.escapeRegExp(this.reqBasePath)}(/.+)`)); @@ -256,7 +263,7 @@ class JWProxy { return commandName; } - async proxyCommand (url, method, body = null) { + async proxyCommand(url, method, body = null) { const commandName = this.requestToCommandName(url, method); if (!commandName) { return await this.proxy(url, method, body); @@ -266,7 +273,7 @@ class JWProxy { return await this.protocolConverter.convertAndProxy(commandName, url, method, body); } - async command (url, method, body = null) { + async command(url, method, body = null) { let response; let resBodyObj; try { @@ -289,7 +296,10 @@ class JWProxy { if (_.has(message, 'message')) { message = message.message; } - throw errorFromMJSONWPStatusCode(status, _.isEmpty(message) ? getSummaryByCode(status) : message); + throw errorFromMJSONWPStatusCode( + status, + _.isEmpty(message) ? getSummaryByCode(status) : message + ); } } else if (protocol === W3C) { // Got response in W3C format @@ -297,22 +307,30 @@ class JWProxy { return resBodyObj.value; } if (_.isPlainObject(resBodyObj.value) && resBodyObj.value.error) { - throw errorFromW3CJsonCode(resBodyObj.value.error, resBodyObj.value.message, resBodyObj.value.stacktrace); + throw errorFromW3CJsonCode( + resBodyObj.value.error, + resBodyObj.value.message, + resBodyObj.value.stacktrace + ); } } else if (response.statusCode === 200) { // Unknown protocol. Keeping it because of the backward compatibility return resBodyObj; } - throw new errors.UnknownError(`Did not know what to do with response code '${response.statusCode}' ` + - `and response body '${_.truncate(JSON.stringify(resBodyObj), {length: 300})}'`); + throw new errors.UnknownError( + `Did not know what to do with response code '${response.statusCode}' ` + + `and response body '${_.truncate(JSON.stringify(resBodyObj), { + length: 300, + })}'` + ); } - getSessionIdFromUrl (url) { + getSessionIdFromUrl(url) { const match = url.match(/\/session\/([^/]+)/); return match ? match[1] : null; } - async proxyReqRes (req, res) { + async proxyReqRes(req, res) { // ! this method must not throw any exceptions // ! make sure to call res.send before return let statusCode; @@ -331,7 +349,7 @@ class JWProxy { if (!_.isPlainObject(resBodyObj)) { const error = new errors.UnknownError( `The downstream server response with the status code ${statusCode} is not a valid JSON object: ` + - _.truncate(`${resBodyObj}`, {length: 300}) + _.truncate(`${resBodyObj}`, {length: 300}) ); [statusCode, resBodyObj] = getResponseForW3CError(error); } @@ -354,5 +372,5 @@ class JWProxy { } } -export { JWProxy }; +export {JWProxy}; export default JWProxy; diff --git a/packages/base-driver/lib/jsonwp-status/status.js b/packages/base-driver/lib/jsonwp-status/status.js index 784f21036..cf3f6bcdc 100644 --- a/packages/base-driver/lib/jsonwp-status/status.js +++ b/packages/base-driver/lib/jsonwp-status/status.js @@ -3,111 +3,118 @@ import _ from 'lodash'; const codes = { Success: { code: 0, - summary: 'The command executed successfully.' + summary: 'The command executed successfully.', }, NoSuchDriver: { code: 6, - summary: 'A session is either terminated or not started' + summary: 'A session is either terminated or not started', }, NoSuchElement: { code: 7, - summary: 'An element could not be located on the page using the given search parameters.' + summary: 'An element could not be located on the page using the given search parameters.', }, NoSuchFrame: { code: 8, - summary: 'A request to switch to a frame could not be satisfied because the frame could not be found.' + summary: + 'A request to switch to a frame could not be satisfied because the frame could not be found.', }, UnknownCommand: { code: 9, - summary: 'The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.' + summary: + 'The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.', }, StaleElementReference: { code: 10, - summary: 'An element command failed because the referenced element is no longer attached to the DOM.' + summary: + 'An element command failed because the referenced element is no longer attached to the DOM.', }, ElementNotVisible: { code: 11, - summary: 'An element command could not be completed because the element is not visible on the page.' + summary: + 'An element command could not be completed because the element is not visible on the page.', }, InvalidElementState: { code: 12, - summary: 'An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element).' + summary: + 'An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element).', }, UnknownError: { code: 13, - summary: 'An unknown server-side error occurred while processing the command.' + summary: 'An unknown server-side error occurred while processing the command.', }, ElementIsNotSelectable: { code: 15, - summary: 'An attempt was made to select an element that cannot be selected.' + summary: 'An attempt was made to select an element that cannot be selected.', }, JavaScriptError: { code: 17, - summary: 'An error occurred while executing user supplied JavaScript.' + summary: 'An error occurred while executing user supplied JavaScript.', }, XPathLookupError: { code: 19, - summary: 'An error occurred while searching for an element by XPath.' + summary: 'An error occurred while searching for an element by XPath.', }, Timeout: { code: 21, - summary: 'An operation did not complete before its timeout expired.' + summary: 'An operation did not complete before its timeout expired.', }, NoSuchWindow: { code: 23, - summary: 'A request to switch to a different window could not be satisfied because the window could not be found.' + summary: + 'A request to switch to a different window could not be satisfied because the window could not be found.', }, InvalidCookieDomain: { code: 24, - summary: 'An illegal attempt was made to set a cookie under a different domain than the current page.' + summary: + 'An illegal attempt was made to set a cookie under a different domain than the current page.', }, UnableToSetCookie: { code: 25, - summary: 'A request to set a cookie\'s value could not be satisfied.' + summary: "A request to set a cookie's value could not be satisfied.", }, UnexpectedAlertOpen: { code: 26, - summary: 'A modal dialog was open, blocking this operation' + summary: 'A modal dialog was open, blocking this operation', }, NoAlertOpenError: { code: 27, - summary: 'An attempt was made to operate on a modal dialog when one was not open.' + summary: 'An attempt was made to operate on a modal dialog when one was not open.', }, ScriptTimeout: { code: 28, - summary: 'A script did not complete before its timeout expired.' + summary: 'A script did not complete before its timeout expired.', }, InvalidElementCoordinates: { code: 29, - summary: 'The coordinates provided to an interactions operation are invalid.' + summary: 'The coordinates provided to an interactions operation are invalid.', }, IMENotAvailable: { code: 30, - summary: 'IME was not available.' + summary: 'IME was not available.', }, IMEEngineActivationFailed: { code: 31, - summary: 'An IME engine could not be started.' + summary: 'An IME engine could not be started.', }, InvalidSelector: { code: 32, - summary: 'Argument was an invalid selector (e.g. XPath/CSS).' + summary: 'Argument was an invalid selector (e.g. XPath/CSS).', }, SessionNotCreatedException: { code: 33, - summary: 'A new session could not be created.' + summary: 'A new session could not be created.', }, MoveTargetOutOfBounds: { code: 34, - summary: 'Target provided for a move action is out of bounds.' + summary: 'Target provided for a move action is out of bounds.', }, NoSuchContext: { code: 35, - summary: 'No such context found.' - } + summary: 'No such context found.', + }, }; -function getSummaryByCode (code) { +function getSummaryByCode(code) { code = parseInt(code, 10); for (let obj of _.values(codes)) { if (!_.isUndefined(obj.code) && obj.code === code) { @@ -118,4 +125,4 @@ function getSummaryByCode (code) { } export default codes; -export { codes, getSummaryByCode }; +export {codes, getSummaryByCode}; diff --git a/packages/base-driver/lib/protocol/errors.js b/packages/base-driver/lib/protocol/errors.js index c0f7bbf3c..01d9a37bb 100644 --- a/packages/base-driver/lib/protocol/errors.js +++ b/packages/base-driver/lib/protocol/errors.js @@ -1,7 +1,7 @@ import ES6Error from 'es6-error'; import _ from 'lodash'; -import { util, logger } from '@appium/support'; -import { StatusCodes as HTTPStatusCodes } from 'http-status-codes'; +import {util, logger} from '@appium/support'; +import {StatusCodes as HTTPStatusCodes} from 'http-status-codes'; const mjsonwpLog = logger.getLogger('MJSONWP'); const w3cLog = logger.getLogger('W3C'); @@ -10,7 +10,7 @@ const W3C_UNKNOWN_ERROR = 'unknown error'; // base error class for all of our errors export class ProtocolError extends ES6Error { - constructor (msg, jsonwpCode, w3cStatus, error) { + constructor(msg, jsonwpCode, w3cStatus, error) { super(msg); this.jsonwpCode = jsonwpCode; this.error = error || W3C_UNKNOWN_ERROR; @@ -21,11 +21,11 @@ export class ProtocolError extends ES6Error { this._stacktrace = null; } - get stacktrace () { + get stacktrace() { return this._stacktrace || this.stack; } - set stacktrace (value) { + set stacktrace(value) { this._stacktrace = value; } } @@ -35,587 +35,719 @@ export class ProtocolError extends ES6Error { // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-error-code export class NoSuchDriverError extends ProtocolError { - static code () { + static code() { return 6; } // W3C Error is called InvalidSessionID - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.NOT_FOUND; } - static error () { + static error() { return 'invalid session id'; } - constructor (err) { - super(err || 'A session is either terminated or not started', NoSuchDriverError.code(), - NoSuchDriverError.w3cStatus(), NoSuchDriverError.error()); + constructor(err) { + super( + err || 'A session is either terminated or not started', + NoSuchDriverError.code(), + NoSuchDriverError.w3cStatus(), + NoSuchDriverError.error() + ); } } export class NoSuchElementError extends ProtocolError { - static code () { + static code() { return 7; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.NOT_FOUND; } - static error () { + static error() { return 'no such element'; } - constructor (err) { - super(err || 'An element could not be located on the page using the given ' + - 'search parameters.', NoSuchElementError.code(), NoSuchElementError.w3cStatus(), - NoSuchElementError.error()); + constructor(err) { + super( + err || 'An element could not be located on the page using the given ' + 'search parameters.', + NoSuchElementError.code(), + NoSuchElementError.w3cStatus(), + NoSuchElementError.error() + ); } } export class NoSuchFrameError extends ProtocolError { - static code () { + static code() { return 8; } - static error () { + static error() { return 'no such frame'; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.NOT_FOUND; } - constructor (err) { - super(err || 'A request to switch to a frame could not be satisfied because ' + - 'the frame could not be found.', NoSuchFrameError.code(), - NoSuchFrameError.w3cStatus(), NoSuchFrameError.error()); + constructor(err) { + super( + err || + 'A request to switch to a frame could not be satisfied because ' + + 'the frame could not be found.', + NoSuchFrameError.code(), + NoSuchFrameError.w3cStatus(), + NoSuchFrameError.error() + ); } } export class UnknownCommandError extends ProtocolError { - static code () { + static code() { return 9; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.NOT_FOUND; } - static error () { + static error() { return 'unknown command'; } - constructor (err) { - super(err || 'The requested resource could not be found, or a request was ' + + constructor(err) { + super( + err || + 'The requested resource could not be found, or a request was ' + 'received using an HTTP method that is not supported by the mapped ' + - 'resource.', UnknownCommandError.code(), UnknownCommandError.w3cStatus(), UnknownCommandError.error()); + 'resource.', + UnknownCommandError.code(), + UnknownCommandError.w3cStatus(), + UnknownCommandError.error() + ); } } export class StaleElementReferenceError extends ProtocolError { - static code () { + static code() { return 10; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.NOT_FOUND; } - static error () { + static error() { return 'stale element reference'; } - constructor (err) { - super(err || 'An element command failed because the referenced element is no ' + - 'longer attached to the DOM.', StaleElementReferenceError.code(), - StaleElementReferenceError.w3cStatus(), StaleElementReferenceError.error()); + constructor(err) { + super( + err || + 'An element command failed because the referenced element is no ' + + 'longer attached to the DOM.', + StaleElementReferenceError.code(), + StaleElementReferenceError.w3cStatus(), + StaleElementReferenceError.error() + ); } } export class ElementNotVisibleError extends ProtocolError { - static code () { + static code() { return 11; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.BAD_REQUEST; } - static error () { + static error() { return 'element not visible'; } - constructor (err) { - super(err || 'An element command could not be completed because the element is ' + - 'not visible on the page.', ElementNotVisibleError.code(), - ElementNotVisibleError.w3cStatus(), ElementNotVisibleError.error()); + constructor(err) { + super( + err || + 'An element command could not be completed because the element is ' + + 'not visible on the page.', + ElementNotVisibleError.code(), + ElementNotVisibleError.w3cStatus(), + ElementNotVisibleError.error() + ); } } export class InvalidElementStateError extends ProtocolError { - static code () { + static code() { return 12; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.BAD_REQUEST; } - static error () { + static error() { return 'invalid element state'; } - constructor (err) { - super(err || 'An element command could not be completed because the element is ' + + constructor(err) { + super( + err || + 'An element command could not be completed because the element is ' + 'in an invalid state (e.g. attempting to click a disabled element).', - InvalidElementStateError.code(), InvalidElementStateError.w3cStatus(), - InvalidElementStateError.error()); + InvalidElementStateError.code(), + InvalidElementStateError.w3cStatus(), + InvalidElementStateError.error() + ); } } export class UnknownError extends ProtocolError { - static code () { + static code() { return 13; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.INTERNAL_SERVER_ERROR; } - static error () { + static error() { return W3C_UNKNOWN_ERROR; } - constructor (errorOrMessage) { + constructor(errorOrMessage) { const origMessage = _.isString((errorOrMessage || {}).message) ? errorOrMessage.message : errorOrMessage; - const message = 'An unknown server-side error occurred while processing the command.' + + const message = + 'An unknown server-side error occurred while processing the command.' + (origMessage ? ` Original error: ${origMessage}` : ''); super(message, UnknownError.code(), UnknownError.w3cStatus(), UnknownError.error()); } } export class UnknownMethodError extends ProtocolError { - static code () { + static code() { return 405; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.METHOD_NOT_ALLOWED; } - static error () { + static error() { return 'unknown method'; } - constructor (err) { - super(err || 'The requested command matched a known URL but did not match an method for that URL', - UnknownMethodError.code(), UnknownMethodError.w3cStatus(), UnknownMethodError.error()); + constructor(err) { + super( + err || 'The requested command matched a known URL but did not match an method for that URL', + UnknownMethodError.code(), + UnknownMethodError.w3cStatus(), + UnknownMethodError.error() + ); } } export class UnsupportedOperationError extends ProtocolError { - static code () { + static code() { return 405; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.INTERNAL_SERVER_ERROR; } - static error () { + static error() { return 'unsupported operation'; } - constructor (err) { - super(err || 'A server-side error occurred. Command cannot be supported.', - UnsupportedOperationError.code(), UnsupportedOperationError.w3cStatus(), - UnsupportedOperationError.error()); + constructor(err) { + super( + err || 'A server-side error occurred. Command cannot be supported.', + UnsupportedOperationError.code(), + UnsupportedOperationError.w3cStatus(), + UnsupportedOperationError.error() + ); } } export class ElementIsNotSelectableError extends ProtocolError { - static code () { + static code() { return 15; } - static error () { + static error() { return 'element not selectable'; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.BAD_REQUEST; } - constructor (err) { - super(err || 'An attempt was made to select an element that cannot be selected.', - ElementIsNotSelectableError.code(), ElementIsNotSelectableError.w3cStatus(), - ElementIsNotSelectableError.error()); + constructor(err) { + super( + err || 'An attempt was made to select an element that cannot be selected.', + ElementIsNotSelectableError.code(), + ElementIsNotSelectableError.w3cStatus(), + ElementIsNotSelectableError.error() + ); } } export class ElementClickInterceptedError extends ProtocolError { - static code () { + static code() { return 64; } - static error () { + static error() { return 'element click intercepted'; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.BAD_REQUEST; } - constructor (err) { - super(err || 'The Element Click command could not be completed because the element receiving ' + + constructor(err) { + super( + err || + 'The Element Click command could not be completed because the element receiving ' + 'the events is obscuring the element that was requested clicked', - ElementClickInterceptedError.code(), ElementClickInterceptedError.w3cStatus(), - ElementClickInterceptedError.error()); + ElementClickInterceptedError.code(), + ElementClickInterceptedError.w3cStatus(), + ElementClickInterceptedError.error() + ); } } export class ElementNotInteractableError extends ProtocolError { - static code () { + static code() { return 60; } - static error () { + static error() { return 'element not interactable'; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.BAD_REQUEST; } - constructor (err) { - super(err || 'A command could not be completed because the element is not pointer- or keyboard interactable', - ElementNotInteractableError.code(), ElementNotInteractableError.w3cStatus(), - ElementNotInteractableError.error()); + constructor(err) { + super( + err || + 'A command could not be completed because the element is not pointer- or keyboard interactable', + ElementNotInteractableError.code(), + ElementNotInteractableError.w3cStatus(), + ElementNotInteractableError.error() + ); } } export class InsecureCertificateError extends ProtocolError { - static error () { + static error() { return 'insecure certificate'; } - constructor (err) { - super(err || 'Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate', - ElementIsNotSelectableError.code(), null, InsecureCertificateError.error()); + constructor(err) { + super( + err || + 'Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate', + ElementIsNotSelectableError.code(), + null, + InsecureCertificateError.error() + ); } } export class JavaScriptError extends ProtocolError { - static code () { + static code() { return 17; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.INTERNAL_SERVER_ERROR; } - static error () { + static error() { return 'javascript error'; } - constructor (err) { - super(err || 'An error occurred while executing user supplied JavaScript.', - JavaScriptError.code(), JavaScriptError.w3cStatus(), JavaScriptError.error()); + constructor(err) { + super( + err || 'An error occurred while executing user supplied JavaScript.', + JavaScriptError.code(), + JavaScriptError.w3cStatus(), + JavaScriptError.error() + ); } } export class XPathLookupError extends ProtocolError { - static code () { + static code() { return 19; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.BAD_REQUEST; } - static error () { + static error() { return 'invalid selector'; } - constructor (err) { - super(err || 'An error occurred while searching for an element by XPath.', - XPathLookupError.code(), XPathLookupError.w3cStatus(), XPathLookupError.error()); + constructor(err) { + super( + err || 'An error occurred while searching for an element by XPath.', + XPathLookupError.code(), + XPathLookupError.w3cStatus(), + XPathLookupError.error() + ); } } export class TimeoutError extends ProtocolError { - static code () { + static code() { return 21; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.REQUEST_TIMEOUT; } - static error () { + static error() { return 'timeout'; } - constructor (err) { - super(err || 'An operation did not complete before its timeout expired.', - TimeoutError.code(), TimeoutError.w3cStatus(), TimeoutError.error()); + constructor(err) { + super( + err || 'An operation did not complete before its timeout expired.', + TimeoutError.code(), + TimeoutError.w3cStatus(), + TimeoutError.error() + ); } } export class NoSuchWindowError extends ProtocolError { - static code () { + static code() { return 23; } - static error () { + static error() { return 'no such window'; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.NOT_FOUND; } - constructor (err) { - super(err || 'A request to switch to a different window could not be satisfied ' + - 'because the window could not be found.', NoSuchWindowError.code(), - NoSuchWindowError.w3cStatus(), NoSuchWindowError.error()); + constructor(err) { + super( + err || + 'A request to switch to a different window could not be satisfied ' + + 'because the window could not be found.', + NoSuchWindowError.code(), + NoSuchWindowError.w3cStatus(), + NoSuchWindowError.error() + ); } } export class InvalidArgumentError extends ProtocolError { - static code () { + static code() { return 61; } - static error () { + static error() { return 'invalid argument'; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.BAD_REQUEST; } - constructor (err) { - super(err || 'The arguments passed to the command are either invalid or malformed', - InvalidArgumentError.code(), InvalidArgumentError.w3cStatus(), - InvalidArgumentError.error()); + constructor(err) { + super( + err || 'The arguments passed to the command are either invalid or malformed', + InvalidArgumentError.code(), + InvalidArgumentError.w3cStatus(), + InvalidArgumentError.error() + ); } } export class InvalidCookieDomainError extends ProtocolError { - static code () { + static code() { return 24; } - static error () { + static error() { return 'invalid cookie domain'; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.BAD_REQUEST; } - constructor (err) { - super(err || 'An illegal attempt was made to set a cookie under a different ' + - 'domain than the current page.', InvalidCookieDomainError.code(), - InvalidCookieDomainError.w3cStatus(), InvalidCookieDomainError.error()); + constructor(err) { + super( + err || + 'An illegal attempt was made to set a cookie under a different ' + + 'domain than the current page.', + InvalidCookieDomainError.code(), + InvalidCookieDomainError.w3cStatus(), + InvalidCookieDomainError.error() + ); } } export class NoSuchCookieError extends ProtocolError { - static code () { + static code() { return 62; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.NOT_FOUND; } - static error () { + static error() { return 'no such cookie'; } - constructor (err) { - super(err || 'No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document', - NoSuchCookieError.code(), NoSuchCookieError.w3cStatus(), NoSuchCookieError.error()); + constructor(err) { + super( + err || + 'No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document', + NoSuchCookieError.code(), + NoSuchCookieError.w3cStatus(), + NoSuchCookieError.error() + ); } } export class UnableToSetCookieError extends ProtocolError { - static code () { + static code() { return 25; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.INTERNAL_SERVER_ERROR; } - static error () { + static error() { return 'unable to set cookie'; } - constructor (err) { - super(err || 'A request to set a cookie\'s value could not be satisfied.', - UnableToSetCookieError.code(), UnableToSetCookieError.w3cStatus(), UnableToSetCookieError.error()); + constructor(err) { + super( + err || "A request to set a cookie's value could not be satisfied.", + UnableToSetCookieError.code(), + UnableToSetCookieError.w3cStatus(), + UnableToSetCookieError.error() + ); } } export class UnexpectedAlertOpenError extends ProtocolError { - static code () { + static code() { return 26; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.INTERNAL_SERVER_ERROR; } - static error () { + static error() { return 'unexpected alert open'; } - constructor (err) { - super(err || 'A modal dialog was open, blocking this operation', - UnexpectedAlertOpenError.code(), UnexpectedAlertOpenError.w3cStatus(), UnexpectedAlertOpenError.error()); + constructor(err) { + super( + err || 'A modal dialog was open, blocking this operation', + UnexpectedAlertOpenError.code(), + UnexpectedAlertOpenError.w3cStatus(), + UnexpectedAlertOpenError.error() + ); } } export class NoAlertOpenError extends ProtocolError { - static code () { + static code() { return 27; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.NOT_FOUND; } - static error () { + static error() { return 'no such alert'; } - constructor (err) { - super(err || 'An attempt was made to operate on a modal dialog when one ' + - 'was not open.', NoAlertOpenError.code(), NoAlertOpenError.w3cStatus(), NoAlertOpenError.error()); + constructor(err) { + super( + err || 'An attempt was made to operate on a modal dialog when one ' + 'was not open.', + NoAlertOpenError.code(), + NoAlertOpenError.w3cStatus(), + NoAlertOpenError.error() + ); } } export class NoSuchAlertError extends NoAlertOpenError {} export class ScriptTimeoutError extends ProtocolError { - static code () { + static code() { return 28; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.REQUEST_TIMEOUT; } - static error () { + static error() { return 'script timeout'; } - constructor (err) { - super(err || 'A script did not complete before its timeout expired.', - ScriptTimeoutError.code(), ScriptTimeoutError.w3cStatus(), ScriptTimeoutError.error()); + constructor(err) { + super( + err || 'A script did not complete before its timeout expired.', + ScriptTimeoutError.code(), + ScriptTimeoutError.w3cStatus(), + ScriptTimeoutError.error() + ); } } export class InvalidElementCoordinatesError extends ProtocolError { - static code () { + static code() { return 29; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.BAD_REQUEST; } - static error () { + static error() { return 'invalid coordinates'; } - constructor (err) { - super(err || 'The coordinates provided to an interactions operation are invalid.', - InvalidElementCoordinatesError.code(), InvalidElementCoordinatesError.w3cStatus(), - InvalidElementCoordinatesError.error()); + constructor(err) { + super( + err || 'The coordinates provided to an interactions operation are invalid.', + InvalidElementCoordinatesError.code(), + InvalidElementCoordinatesError.w3cStatus(), + InvalidElementCoordinatesError.error() + ); } } export class InvalidCoordinatesError extends InvalidElementCoordinatesError {} export class IMENotAvailableError extends ProtocolError { - static code () { + static code() { return 30; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.INTERNAL_SERVER_ERROR; } - static error () { + static error() { return 'unsupported operation'; } - constructor (err) { - super(err || 'IME was not available.', IMENotAvailableError.code(), - IMENotAvailableError.w3cStatus(), IMENotAvailableError.error()); + constructor(err) { + super( + err || 'IME was not available.', + IMENotAvailableError.code(), + IMENotAvailableError.w3cStatus(), + IMENotAvailableError.error() + ); } } export class IMEEngineActivationFailedError extends ProtocolError { - static code () { + static code() { return 31; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.INTERNAL_SERVER_ERROR; } - static error () { + static error() { return 'unsupported operation'; } - constructor (err) { - super(err || 'An IME engine could not be started.', - IMEEngineActivationFailedError.code(), IMEEngineActivationFailedError.w3cStatus(), - IMEEngineActivationFailedError.error()); + constructor(err) { + super( + err || 'An IME engine could not be started.', + IMEEngineActivationFailedError.code(), + IMEEngineActivationFailedError.w3cStatus(), + IMEEngineActivationFailedError.error() + ); } } export class InvalidSelectorError extends ProtocolError { - static code () { + static code() { return 32; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.BAD_REQUEST; } - static error () { + static error() { return 'invalid selector'; } - constructor (err) { - super(err || 'Argument was an invalid selector (e.g. XPath/CSS).', - InvalidSelectorError.code(), InvalidSelectorError.w3cStatus(), - InvalidSelectorError.error()); + constructor(err) { + super( + err || 'Argument was an invalid selector (e.g. XPath/CSS).', + InvalidSelectorError.code(), + InvalidSelectorError.w3cStatus(), + InvalidSelectorError.error() + ); } } export class SessionNotCreatedError extends ProtocolError { - static code () { + static code() { return 33; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.INTERNAL_SERVER_ERROR; } - static error () { + static error() { return 'session not created'; } - constructor (details) { + constructor(details) { let message = 'A new session could not be created.'; if (details) { message += ` Details: ${details}`; } - super(message, SessionNotCreatedError.code(), SessionNotCreatedError.w3cStatus(), SessionNotCreatedError.error()); + super( + message, + SessionNotCreatedError.code(), + SessionNotCreatedError.w3cStatus(), + SessionNotCreatedError.error() + ); } } export class MoveTargetOutOfBoundsError extends ProtocolError { - static code () { + static code() { return 34; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.INTERNAL_SERVER_ERROR; } - static error () { + static error() { return 'move target out of bounds'; } - constructor (err) { - super(err || 'Target provided for a move action is out of bounds.', - MoveTargetOutOfBoundsError.code(), MoveTargetOutOfBoundsError.w3cStatus(), MoveTargetOutOfBoundsError.error()); + constructor(err) { + super( + err || 'Target provided for a move action is out of bounds.', + MoveTargetOutOfBoundsError.code(), + MoveTargetOutOfBoundsError.w3cStatus(), + MoveTargetOutOfBoundsError.error() + ); } } export class NoSuchContextError extends ProtocolError { - static code () { + static code() { return 35; } - constructor (err) { + constructor(err) { super(err || 'No such context found.', NoSuchContextError.code()); } } export class InvalidContextError extends ProtocolError { - static code () { + static code() { return 36; } - constructor (err) { - super(err || 'That command could not be executed in the current context.', - InvalidContextError.code()); + constructor(err) { + super( + err || 'That command could not be executed in the current context.', + InvalidContextError.code() + ); } } // These are aliases for UnknownMethodError export class NotYetImplementedError extends UnknownMethodError { - constructor (err) { + constructor(err) { super(err || 'Method has not yet been implemented'); } } export class NotImplementedError extends UnknownMethodError { - constructor (err) { + constructor(err) { super(err || 'Method is not implemented'); } } export class UnableToCaptureScreen extends ProtocolError { - static code () { + static code() { return 63; } - static w3cStatus () { + static w3cStatus() { return HTTPStatusCodes.INTERNAL_SERVER_ERROR; } - static error () { + static error() { return 'unable to capture screen'; } - constructor (err) { - super(err || 'A screen capture was made impossible', - UnableToCaptureScreen.code(), UnableToCaptureScreen.w3cStatus(), UnableToCaptureScreen.error()); + constructor(err) { + super( + err || 'A screen capture was made impossible', + UnableToCaptureScreen.code(), + UnableToCaptureScreen.w3cStatus(), + UnableToCaptureScreen.error() + ); } } - // Equivalent to W3C InvalidArgumentError export class BadParametersError extends ES6Error { - static error () { + static error() { return 'invalid argument'; } - constructor (requiredParams, actualParams, errMessage) { + constructor(requiredParams, actualParams, errMessage) { let message; if (!errMessage) { - message = `Parameters were incorrect. We wanted ` + - `${JSON.stringify(requiredParams)} and you ` + - `sent ${JSON.stringify(actualParams)}`; + message = + `Parameters were incorrect. We wanted ` + + `${JSON.stringify(requiredParams)} and you ` + + `sent ${JSON.stringify(actualParams)}`; } else { - message = `Parameters were incorrect. You sent ${JSON.stringify(actualParams)}, ${errMessage}`; + message = `Parameters were incorrect. You sent ${JSON.stringify( + actualParams + )}, ${errMessage}`; } super(message); this.w3cStatus = HTTPStatusCodes.BAD_REQUEST; @@ -629,7 +761,7 @@ export class BadParametersError extends ES6Error { * for proxy failure to generate the client response. */ export class ProxyRequestError extends ES6Error { - constructor (err, responseError, httpStatus) { + constructor(err, responseError, httpStatus) { let responseErrorObj = util.safeJsonParse(responseError); if (!_.isPlainObject(responseErrorObj)) { responseErrorObj = {}; @@ -638,7 +770,10 @@ export class ProxyRequestError extends ES6Error { if (!_.isEmpty(responseErrorObj)) { if (_.isString(responseErrorObj.value)) { origMessage = responseErrorObj.value; - } else if (_.isPlainObject(responseErrorObj.value) && _.isString(responseErrorObj.value.message)) { + } else if ( + _.isPlainObject(responseErrorObj.value) && + _.isString(responseErrorObj.value.message) + ) { origMessage = responseErrorObj.value.message; } } @@ -655,57 +790,63 @@ export class ProxyRequestError extends ES6Error { } } - getActualError () { + getActualError() { // If it's MJSONWP error, returns actual error cause for request failure based on `jsonwp.status` if (util.hasValue(this.jsonwp?.status) && util.hasValue(this.jsonwp?.value)) { return errorFromMJSONWPStatusCode(this.jsonwp.status, this.jsonwp.value); } else if (util.hasValue(this.w3c) && _.isNumber(this.w3cStatus) && this.w3cStatus >= 300) { - return errorFromW3CJsonCode(this.w3c.error, this.w3c.message || this.message, this.w3c.stacktrace); + return errorFromW3CJsonCode( + this.w3c.error, + this.w3c.message || this.message, + this.w3c.stacktrace + ); } return new UnknownError(this.message); } } // map of error class name to error class -const errors = {NotYetImplementedError, - NotImplementedError, - BadParametersError, - InvalidArgumentError, - NoSuchDriverError, - NoSuchElementError, - UnknownCommandError, - StaleElementReferenceError, - ElementNotVisibleError, - InvalidElementStateError, - UnknownError, - ElementIsNotSelectableError, - ElementClickInterceptedError, - ElementNotInteractableError, - InsecureCertificateError, - JavaScriptError, - XPathLookupError, - TimeoutError, - NoSuchWindowError, - NoSuchCookieError, - InvalidCookieDomainError, - InvalidCoordinatesError, - UnableToSetCookieError, - UnexpectedAlertOpenError, - NoAlertOpenError, - ScriptTimeoutError, - InvalidElementCoordinatesError, - IMENotAvailableError, - IMEEngineActivationFailedError, - InvalidSelectorError, - SessionNotCreatedError, - MoveTargetOutOfBoundsError, - NoSuchAlertError, - NoSuchContextError, - InvalidContextError, - NoSuchFrameError, - UnableToCaptureScreen, - UnknownMethodError, - UnsupportedOperationError, - ProxyRequestError}; +const errors = { + NotYetImplementedError, + NotImplementedError, + BadParametersError, + InvalidArgumentError, + NoSuchDriverError, + NoSuchElementError, + UnknownCommandError, + StaleElementReferenceError, + ElementNotVisibleError, + InvalidElementStateError, + UnknownError, + ElementIsNotSelectableError, + ElementClickInterceptedError, + ElementNotInteractableError, + InsecureCertificateError, + JavaScriptError, + XPathLookupError, + TimeoutError, + NoSuchWindowError, + NoSuchCookieError, + InvalidCookieDomainError, + InvalidCoordinatesError, + UnableToSetCookieError, + UnexpectedAlertOpenError, + NoAlertOpenError, + ScriptTimeoutError, + InvalidElementCoordinatesError, + IMENotAvailableError, + IMEEngineActivationFailedError, + InvalidSelectorError, + SessionNotCreatedError, + MoveTargetOutOfBoundsError, + NoSuchAlertError, + NoSuchContextError, + InvalidContextError, + NoSuchFrameError, + UnableToCaptureScreen, + UnknownMethodError, + UnsupportedOperationError, + ProxyRequestError, +}; // map of error code to error class const jsonwpErrorCodeMap = {}; @@ -722,14 +863,16 @@ for (let ErrorClass of _.values(errors)) { } } -function isUnknownError (err) { - return !err.constructor.name || - !_.values(errors).find(function equalNames (error) { - return error.name === err.constructor.name; - }); +function isUnknownError(err) { + return ( + !err.constructor.name || + !_.values(errors).find(function equalNames(error) { + return error.name === err.constructor.name; + }) + ); } -function isErrorType (err, type) { +function isErrorType(err, type) { // `name` property is the constructor name if (type.name === ProtocolError.name) { // `jsonwpCode` is `0` on success @@ -755,7 +898,7 @@ function isErrorType (err, type) { * @param {string|Object} value The error message, or an object with a `message` property * @return {ProtocolError} The error that is associated with provided JSONWP status code */ -function errorFromMJSONWPStatusCode (code, value = '') { +function errorFromMJSONWPStatusCode(code, value = '') { // if `value` is an object, pull message from it, otherwise use the plain // value, or default to an empty string, if null const message = (value || {}).message || value || ''; @@ -774,7 +917,7 @@ function errorFromMJSONWPStatusCode (code, value = '') { * @param {?string} stacktrace an optional error stacktrace * @return {ProtocolError} The error that is associated with the W3C error string */ -function errorFromW3CJsonCode (code, message, stacktrace = null) { +function errorFromW3CJsonCode(code, message, stacktrace = null) { if (code && w3cErrorCodeMap[code.toLowerCase()]) { w3cLog.debug(`Matched W3C error code '${code}' to ${w3cErrorCodeMap[code.toLowerCase()].name}`); const resultError = new w3cErrorCodeMap[code.toLowerCase()](message); @@ -791,7 +934,7 @@ function errorFromW3CJsonCode (code, message, stacktrace = null) { * Convert an Appium error to proper W3C HTTP response * @param {ProtocolError} err The error that needs to be translated */ -function getResponseForW3CError (err) { +function getResponseForW3CError(err) { let httpStatus; // W3C defined error message (https://www.w3.org/TR/webdriver/#dfn-error-code) @@ -799,8 +942,8 @@ function getResponseForW3CError (err) { if (!err.w3cStatus) { err = util.hasValue(err.status) - // If it's a JSONWP error, find corresponding error - ? errorFromMJSONWPStatusCode(err.status, err.value) + ? // If it's a JSONWP error, find corresponding error + errorFromMJSONWPStatusCode(err.status, err.value) : new errors.UnknownError(err.message); } @@ -823,7 +966,7 @@ function getResponseForW3CError (err) { error: w3cErrorString, message: err.message, stacktrace: err.stacktrace || err.stack, - } + }, }; return [httpStatus, httpResBody]; } @@ -832,7 +975,7 @@ function getResponseForW3CError (err) { * Convert an Appium error to a proper JSONWP response * @param {ProtocolError} err The error to be converted */ -function getResponseForJsonwpError (err) { +function getResponseForJsonwpError(err) { if (isUnknownError(err)) { err = new errors.UnknownError(err); } @@ -841,8 +984,8 @@ function getResponseForJsonwpError (err) { let httpResBody = { status: err.jsonwpCode, value: { - message: err.message - } + message: err.message, + }, }; if (isErrorType(err, errors.BadParametersError)) { @@ -850,8 +993,10 @@ function getResponseForJsonwpError (err) { mjsonwpLog.debug(`Bad parameters: ${err}`); httpStatus = HTTPStatusCodes.BAD_REQUEST; httpResBody = err.message; - } else if (isErrorType(err, errors.NotYetImplementedError) || - isErrorType(err, errors.NotImplementedError)) { + } else if ( + isErrorType(err, errors.NotYetImplementedError) || + isErrorType(err, errors.NotImplementedError) + ) { // respond with a 501 if the method is not implemented httpStatus = HTTPStatusCodes.NOT_IMPLEMENTED; } else if (isErrorType(err, errors.NoSuchDriverError)) { @@ -859,12 +1004,15 @@ function getResponseForJsonwpError (err) { httpStatus = HTTPStatusCodes.NOT_FOUND; } - return [httpStatus, httpResBody]; } export { - errors, isErrorType, isUnknownError, - errorFromMJSONWPStatusCode, errorFromW3CJsonCode, - getResponseForW3CError, getResponseForJsonwpError, + errors, + isErrorType, + isUnknownError, + errorFromMJSONWPStatusCode, + errorFromW3CJsonCode, + getResponseForW3CError, + getResponseForJsonwpError, }; diff --git a/packages/base-driver/lib/protocol/helpers.js b/packages/base-driver/lib/protocol/helpers.js index d855d2bba..98ec0e8d0 100644 --- a/packages/base-driver/lib/protocol/helpers.js +++ b/packages/base-driver/lib/protocol/helpers.js @@ -1,6 +1,6 @@ import _ from 'lodash'; -import { duplicateKeys } from '../basedriver/helpers'; -import { MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY } from '../constants'; +import {duplicateKeys} from '../basedriver/helpers'; +import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY} from '../constants'; /** * Preprocesses the resulting value for API responses, @@ -11,7 +11,7 @@ import { MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY } from '../constants'; * @returns {?Object} Either modified value or the same one if * nothing has been modified */ -function formatResponseValue (resValue) { +function formatResponseValue(resValue) { if (_.isUndefined(resValue)) { // convert undefined to null return null; @@ -28,11 +28,8 @@ function formatResponseValue (resValue) { * @param {Object} responseBody * @returns {Object} The fixed response body */ -function formatStatus (responseBody) { +function formatStatus(responseBody) { return _.isPlainObject(responseBody) ? _.omit(responseBody, ['status']) : responseBody; } - -export { - MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, formatResponseValue, formatStatus -}; +export {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, formatResponseValue, formatStatus}; diff --git a/packages/base-driver/lib/protocol/index.js b/packages/base-driver/lib/protocol/index.js index c8d620bcd..f7bc0d3ad 100644 --- a/packages/base-driver/lib/protocol/index.js +++ b/packages/base-driver/lib/protocol/index.js @@ -1,20 +1,29 @@ // transpile:main import { - isSessionCommand, routeConfiguringFunction, determineProtocol, - CREATE_SESSION_COMMAND, DELETE_SESSION_COMMAND, GET_STATUS_COMMAND + isSessionCommand, + routeConfiguringFunction, + determineProtocol, + CREATE_SESSION_COMMAND, + DELETE_SESSION_COMMAND, + GET_STATUS_COMMAND, } from './protocol'; -import { - NO_SESSION_ID_COMMANDS, ALL_COMMANDS, METHOD_MAP, - routeToCommandName -} from './routes'; -import { - errors, isErrorType, errorFromMJSONWPStatusCode, errorFromW3CJsonCode -} from './errors'; +import {NO_SESSION_ID_COMMANDS, ALL_COMMANDS, METHOD_MAP, routeToCommandName} from './routes'; +import {errors, isErrorType, errorFromMJSONWPStatusCode, errorFromW3CJsonCode} from './errors'; export { - routeConfiguringFunction, errors, isErrorType, - errorFromMJSONWPStatusCode, errorFromW3CJsonCode, ALL_COMMANDS, METHOD_MAP, - routeToCommandName, NO_SESSION_ID_COMMANDS, isSessionCommand, determineProtocol, - CREATE_SESSION_COMMAND, DELETE_SESSION_COMMAND, GET_STATUS_COMMAND + routeConfiguringFunction, + errors, + isErrorType, + errorFromMJSONWPStatusCode, + errorFromW3CJsonCode, + ALL_COMMANDS, + METHOD_MAP, + routeToCommandName, + NO_SESSION_ID_COMMANDS, + isSessionCommand, + determineProtocol, + CREATE_SESSION_COMMAND, + DELETE_SESSION_COMMAND, + GET_STATUS_COMMAND, }; diff --git a/packages/base-driver/lib/protocol/protocol.js b/packages/base-driver/lib/protocol/protocol.js index 343e0efab..c1342022a 100644 --- a/packages/base-driver/lib/protocol/protocol.js +++ b/packages/base-driver/lib/protocol/protocol.js @@ -1,26 +1,28 @@ import _ from 'lodash'; -import { util, logger, node } from '@appium/support'; -import { validators } from './validators'; +import {util, logger, node} from '@appium/support'; +import {validators} from './validators'; import { - errors, isErrorType, getResponseForW3CError, - errorFromMJSONWPStatusCode, errorFromW3CJsonCode, + errors, + isErrorType, + getResponseForW3CError, + errorFromMJSONWPStatusCode, + errorFromW3CJsonCode, } from './errors'; -import { METHOD_MAP, NO_SESSION_ID_COMMANDS } from './routes'; +import {METHOD_MAP, NO_SESSION_ID_COMMANDS} from './routes'; import B from 'bluebird'; -import { formatResponseValue, formatStatus } from './helpers'; -import { MAX_LOG_BODY_LENGTH, PROTOCOLS, DEFAULT_BASE_PATH } from '../constants'; -import { isW3cCaps } from '../helpers/capabilities'; - +import {formatResponseValue, formatStatus} from './helpers'; +import {MAX_LOG_BODY_LENGTH, PROTOCOLS, DEFAULT_BASE_PATH} from '../constants'; +import {isW3cCaps} from '../helpers/capabilities'; const CREATE_SESSION_COMMAND = 'createSession'; const DELETE_SESSION_COMMAND = 'deleteSession'; const GET_STATUS_COMMAND = 'getStatus'; -function determineProtocol (createSessionArgs) { +function determineProtocol(createSessionArgs) { return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP; } -function extractProtocol (driver, sessionId = null) { +function extractProtocol(driver, sessionId = null) { const dstDriver = _.isFunction(driver.driverForSession) ? driver.driverForSession(sessionId) : driver; @@ -35,14 +37,15 @@ function extractProtocol (driver, sessionId = null) { return dstDriver?.protocol ?? PROTOCOLS.W3C; } -function isSessionCommand (command) { +function isSessionCommand(command) { return !_.includes(NO_SESSION_ID_COMMANDS, command); } -function getLogger (driver, sessionId = null) { - const dstDriver = sessionId && _.isFunction(driver.driverForSession) - ? (driver.driverForSession(sessionId) ?? driver) - : driver; +function getLogger(driver, sessionId = null) { + const dstDriver = + sessionId && _.isFunction(driver.driverForSession) + ? driver.driverForSession(sessionId) ?? driver + : driver; if (_.isFunction(dstDriver.log?.info)) { return dstDriver.log; } @@ -56,7 +59,7 @@ function getLogger (driver, sessionId = null) { return logger.getLogger(logPrefix); } -function wrapParams (paramSets, jsonObj) { +function wrapParams(paramSets, jsonObj) { /* There are commands like performTouch which take a single parameter (primitive type or array). * Some drivers choose to pass this parameter as a value (eg. [action1, action2...]) while others to * wrap it within an object(eg' {gesture: [action1, action2...]}), which makes it hard to validate. @@ -71,7 +74,7 @@ function wrapParams (paramSets, jsonObj) { return res; } -function unwrapParams (paramSets, jsonObj) { +function unwrapParams(paramSets, jsonObj) { /* There are commands like setNetworkConnection which send parameters wrapped inside a key such as * "parameters". This function unwraps them (eg. {"parameters": {"type": 1}} becomes {"type": 1}). */ @@ -85,7 +88,7 @@ function unwrapParams (paramSets, jsonObj) { return res; } -function checkParams (paramSets, jsonObj, protocol) { +function checkParams(paramSets, jsonObj, protocol) { let requiredParams = []; let optionalParams = []; let receivedParams = _.keys(jsonObj); @@ -134,8 +137,10 @@ function checkParams (paramSets, jsonObj, protocol) { // go through the required parameters and check against our arguments for (let params of requiredParams) { - if (_.difference(receivedParams, params, optionalParams).length === 0 && - _.difference(params, receivedParams).length === 0) { + if ( + _.difference(receivedParams, params, optionalParams).length === 0 && + _.difference(params, receivedParams).length === 0 + ) { // we have a set of parameters that is correct // so short-circuit return; @@ -151,7 +156,7 @@ function checkParams (paramSets, jsonObj, protocol) { * on handling parameters. This method returns an array of arguments which will * be applied to a command. */ -function makeArgs (requestParams, jsonObj, payloadParams, protocol) { +function makeArgs(requestParams, jsonObj, payloadParams, protocol) { // We want to pass the "url" parameters to the commands in reverse order // since the command will sometimes want to ignore, say, the sessionId. // This has the effect of putting sessionId last, which means in JS we can @@ -201,7 +206,7 @@ function makeArgs (requestParams, jsonObj, payloadParams, protocol) { return args; } -function routeConfiguringFunction (driver) { +function routeConfiguringFunction(driver) { if (!driver.sessionExists) { throw new Error('Drivers must implement `sessionExists` property'); } @@ -212,7 +217,7 @@ function routeConfiguringFunction (driver) { // return a function which will add all the routes to the driver. Here extraMethods might be // passed in as defined by Appium plugins, so we need to add those to the default list - return function addRoutes (app, {basePath = DEFAULT_BASE_PATH, extraMethodMap = {}}) { + return function addRoutes(app, {basePath = DEFAULT_BASE_PATH, extraMethodMap = {}}) { // store basePath on the driver instance so it can use it if necessary // for example in determining proxy avoidance driver.basePath = basePath; @@ -222,13 +227,20 @@ function routeConfiguringFunction (driver) { for (const [path, methods] of _.toPairs(allMethods)) { for (const [method, spec] of _.toPairs(methods)) { // set up the express route handler - buildHandler(app, method, `${basePath}${path}`, spec, driver, isSessionCommand(spec.command)); + buildHandler( + app, + method, + `${basePath}${path}`, + spec, + driver, + isSessionCommand(spec.command) + ); } } }; } -function buildHandler (app, method, path, spec, driver, isSessCmd) { +function buildHandler(app, method, path, spec, driver, isSessCmd) { let asyncHandler = async (req, res) => { let jsonObj = req.body; let httpResBody = {}; @@ -253,14 +265,18 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) { // plugin) let didPluginOverrideProxy = false; if (isSessCmd && !spec.neverProxy && driverShouldDoJwpProxy(driver, req, spec.command)) { - if (!driver.pluginsToHandleCmd || - driver.pluginsToHandleCmd(spec.command, req.params.sessionId).length === 0) { + if ( + !driver.pluginsToHandleCmd || + driver.pluginsToHandleCmd(spec.command, req.params.sessionId).length === 0 + ) { await doJwpProxy(driver, req, res); return; } - getLogger(driver, req.params.sessionId).debug(`Would have proxied ` + - `command directly, but a plugin exists which might require its value, so will let ` + - `its value be collected internally and made part of plugin chain`); + getLogger(driver, req.params.sessionId).debug( + `Would have proxied ` + + `command directly, but a plugin exists which might require its value, so will let ` + + `its value be collected internally and made part of plugin chain` + ); didPluginOverrideProxy = true; } @@ -283,7 +299,9 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) { if (spec.command === CREATE_SESSION_COMMAND) { // try to determine protocol by session creation args, so we can throw a // properly formatted error if arguments validation fails - currentProtocol = determineProtocol(makeArgs(req.params, jsonObj, spec.payloadParams || {})); + currentProtocol = determineProtocol( + makeArgs(req.params, jsonObj, spec.payloadParams || {}) + ); } // ensure that the json payload conforms to the spec @@ -299,9 +317,11 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) { } // run the driver command wrapped inside the argument validators - getLogger(driver, req.params.sessionId).debug(`Calling ` + - `${driver.constructor.name}.${spec.command}() with args: ` + - _.truncate(JSON.stringify(args), {length: MAX_LOG_BODY_LENGTH})); + getLogger(driver, req.params.sessionId).debug( + `Calling ` + + `${driver.constructor.name}.${spec.command}() with args: ` + + _.truncate(JSON.stringify(args), {length: MAX_LOG_BODY_LENGTH}) + ); if (didPluginOverrideProxy) { // TODO for now we add this information on the args list, but that's mixing purposes here. @@ -328,8 +348,9 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) { // unpack createSession response if (spec.command === CREATE_SESSION_COMMAND) { newSessionId = driverRes[0]; - getLogger(driver, newSessionId) - .debug(`Cached the protocol value '${currentProtocol}' for the new session ${newSessionId}`); + getLogger(driver, newSessionId).debug( + `Cached the protocol value '${currentProtocol}' for the new session ${newSessionId}` + ); if (currentProtocol === PROTOCOLS.MJSONWP) { driverRes = driverRes[1]; } else if (currentProtocol === PROTOCOLS.W3C) { @@ -343,42 +364,59 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) { // delete should not return anything even if successful if (spec.command === DELETE_SESSION_COMMAND) { - getLogger(driver, req.params.sessionId) - .debug(`Received response: ${_.truncate(JSON.stringify(driverRes), {length: MAX_LOG_BODY_LENGTH})}`); + getLogger(driver, req.params.sessionId).debug( + `Received response: ${_.truncate(JSON.stringify(driverRes), { + length: MAX_LOG_BODY_LENGTH, + })}` + ); getLogger(driver, req.params.sessionId).debug('But deleting session, so not returning'); driverRes = null; } // if the status is not 0, throw the appropriate error for status code. if (util.hasValue(driverRes)) { - if (util.hasValue(driverRes.status) && !isNaN(driverRes.status) && parseInt(driverRes.status, 10) !== 0) { + if ( + util.hasValue(driverRes.status) && + !isNaN(driverRes.status) && + parseInt(driverRes.status, 10) !== 0 + ) { throw errorFromMJSONWPStatusCode(driverRes.status, driverRes.value); } else if (_.isPlainObject(driverRes.value) && driverRes.value.error) { - throw errorFromW3CJsonCode(driverRes.value.error, driverRes.value.message, driverRes.value.stacktrace); + throw errorFromW3CJsonCode( + driverRes.value.error, + driverRes.value.message, + driverRes.value.stacktrace + ); } } httpResBody.value = driverRes; - getLogger(driver, req.params.sessionId || newSessionId).debug(`Responding ` + - `to client with driver.${spec.command}() result: ${_.truncate(JSON.stringify(driverRes), {length: MAX_LOG_BODY_LENGTH})}`); + getLogger(driver, req.params.sessionId || newSessionId).debug( + `Responding ` + + `to client with driver.${spec.command}() result: ${_.truncate(JSON.stringify(driverRes), { + length: MAX_LOG_BODY_LENGTH, + })}` + ); } catch (err) { // if anything goes wrong, figure out what our response should be // based on the type of error that we encountered let actualErr = err; - currentProtocol = currentProtocol || extractProtocol(driver, req.params.sessionId || newSessionId); + currentProtocol = + currentProtocol || extractProtocol(driver, req.params.sessionId || newSessionId); let errMsg = err.stacktrace || err.stack; if (!_.includes(errMsg, err.message)) { // if the message has more information, add it. but often the message // is the first part of the stack trace - errMsg = `${err.message}${errMsg ? ('\n' + errMsg) : ''}`; + errMsg = `${err.message}${errMsg ? '\n' + errMsg : ''}`; } if (isErrorType(err, errors.ProxyRequestError)) { actualErr = err.getActualError(); } else { - getLogger(driver, req.params.sessionId || newSessionId) - .debug(`Encountered internal error running command: ${errMsg}`); + getLogger(driver, req.params.sessionId || newSessionId).debug( + `Encountered internal error running command: ${errMsg}` + ); } [httpStatus, httpResBody] = getResponseForW3CError(actualErr); @@ -412,7 +450,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) { }); } -function driverShouldDoJwpProxy (driver, req, command) { +function driverShouldDoJwpProxy(driver, req, command) { // drivers need to explicitly say when the proxy is active if (!driver.proxyActive(req.params.sessionId)) { return false; @@ -433,9 +471,10 @@ function driverShouldDoJwpProxy (driver, req, command) { return true; } -async function doJwpProxy (driver, req, res) { - getLogger(driver, req.params.sessionId) - .info('Driver proxy active, passing request on via HTTP proxy'); +async function doJwpProxy(driver, req, res) { + getLogger(driver, req.params.sessionId).info( + 'Driver proxy active, passing request on via HTTP proxy' + ); // check that the inner driver has a proxy function if (!driver.canProxy(req.params.sessionId)) { @@ -452,9 +491,12 @@ async function doJwpProxy (driver, req, res) { } } - export { - routeConfiguringFunction, isSessionCommand, - driverShouldDoJwpProxy, determineProtocol, CREATE_SESSION_COMMAND, - DELETE_SESSION_COMMAND, GET_STATUS_COMMAND, + routeConfiguringFunction, + isSessionCommand, + driverShouldDoJwpProxy, + determineProtocol, + CREATE_SESSION_COMMAND, + DELETE_SESSION_COMMAND, + GET_STATUS_COMMAND, }; diff --git a/packages/base-driver/lib/protocol/routes.js b/packages/base-driver/lib/protocol/routes.js index a2c59115d..d87007868 100644 --- a/packages/base-driver/lib/protocol/routes.js +++ b/packages/base-driver/lib/protocol/routes.js @@ -1,13 +1,14 @@ // @ts-check import _ from 'lodash'; -import { util } from '@appium/support'; -import { PROTOCOLS, DEFAULT_BASE_PATH } from '../constants'; - +import {util} from '@appium/support'; +import {PROTOCOLS, DEFAULT_BASE_PATH} from '../constants'; const SET_ALERT_TEXT_PAYLOAD_PARAMS = { - validate: (jsonObj) => (!util.hasValue(jsonObj.value) && !util.hasValue(jsonObj.text)) && - 'either "text" or "value" must be set', + validate: (jsonObj) => + !util.hasValue(jsonObj.value) && + !util.hasValue(jsonObj.text) && + 'either "text" or "value" must be set', optional: ['value', 'text'], // Prefer 'value' since it's more backward-compatible. makeArgs: (jsonObj) => [jsonObj.value || jsonObj.text], @@ -19,184 +20,220 @@ const SET_ALERT_TEXT_PAYLOAD_PARAMS = { /** @type {import('@appium/types').MethodMap} */ const METHOD_MAP = { '/status': { - GET: {command: 'getStatus'} + GET: {command: 'getStatus'}, }, '/session': { - POST: {command: 'createSession', payloadParams: { - validate: (jsonObj) => (!jsonObj.capabilities && !jsonObj.desiredCapabilities) && 'we require one of "desiredCapabilities" or "capabilities" object', - optional: ['desiredCapabilities', 'requiredCapabilities', 'capabilities']}} + POST: { + command: 'createSession', + payloadParams: { + validate: (jsonObj) => + !jsonObj.capabilities && + !jsonObj.desiredCapabilities && + 'we require one of "desiredCapabilities" or "capabilities" object', + optional: ['desiredCapabilities', 'requiredCapabilities', 'capabilities'], + }, + }, }, '/sessions': { - GET: {command: 'getSessions'} + GET: {command: 'getSessions'}, }, '/session/:sessionId': { GET: {command: 'getSession'}, - DELETE: {command: 'deleteSession'} + DELETE: {command: 'deleteSession'}, }, '/session/:sessionId/timeouts': { GET: {command: 'getTimeouts'}, // W3C route - POST: {command: 'timeouts', payloadParams: { - validate: (jsonObj, protocolName) => { - if (protocolName === PROTOCOLS.W3C) { - if (!util.hasValue(jsonObj.script) && !util.hasValue(jsonObj.pageLoad) && !util.hasValue(jsonObj.implicit)) { - return 'W3C protocol expects any of script, pageLoad or implicit to be set'; + POST: { + command: 'timeouts', + payloadParams: { + validate: (jsonObj, protocolName) => { + if (protocolName === PROTOCOLS.W3C) { + if ( + !util.hasValue(jsonObj.script) && + !util.hasValue(jsonObj.pageLoad) && + !util.hasValue(jsonObj.implicit) + ) { + return 'W3C protocol expects any of script, pageLoad or implicit to be set'; + } + } else { + // MJSONWP + if (!util.hasValue(jsonObj.type) || !util.hasValue(jsonObj.ms)) { + return 'MJSONWP protocol requires type and ms'; + } } - } else { - // MJSONWP - if (!util.hasValue(jsonObj.type) || !util.hasValue(jsonObj.ms)) { - return 'MJSONWP protocol requires type and ms'; - } - } + }, + optional: ['type', 'ms', 'script', 'pageLoad', 'implicit'], }, - optional: ['type', 'ms', 'script', 'pageLoad', 'implicit'], - }} + }, }, '/session/:sessionId/timeouts/async_script': { - POST: {command: 'asyncScriptTimeout', payloadParams: {required: ['ms']}} + POST: {command: 'asyncScriptTimeout', payloadParams: {required: ['ms']}}, }, '/session/:sessionId/timeouts/implicit_wait': { - POST: {command: 'implicitWait', payloadParams: {required: ['ms']}} + POST: {command: 'implicitWait', payloadParams: {required: ['ms']}}, }, // JSONWP '/session/:sessionId/window_handle': { - GET: {command: 'getWindowHandle'} + GET: {command: 'getWindowHandle'}, }, // W3C '/session/:sessionId/window/handle': { - GET: {command: 'getWindowHandle'} + GET: {command: 'getWindowHandle'}, }, // JSONWP '/session/:sessionId/window_handles': { - GET: {command: 'getWindowHandles'} + GET: {command: 'getWindowHandles'}, }, // W3C '/session/:sessionId/window/handles': { - GET: {command: 'getWindowHandles'} + GET: {command: 'getWindowHandles'}, }, '/session/:sessionId/url': { GET: {command: 'getUrl'}, - POST: {command: 'setUrl', payloadParams: {required: ['url']}} + POST: {command: 'setUrl', payloadParams: {required: ['url']}}, }, '/session/:sessionId/forward': { - POST: {command: 'forward'} + POST: {command: 'forward'}, }, '/session/:sessionId/back': { - POST: {command: 'back'} + POST: {command: 'back'}, }, '/session/:sessionId/refresh': { - POST: {command: 'refresh'} + POST: {command: 'refresh'}, }, // MJSONWP '/session/:sessionId/execute': { - POST: {command: 'execute', payloadParams: {required: ['script', 'args']}} + POST: {command: 'execute', payloadParams: {required: ['script', 'args']}}, }, // MJSONWP '/session/:sessionId/execute_async': { - POST: {command: 'executeAsync', payloadParams: {required: ['script', 'args']}} + POST: { + command: 'executeAsync', + payloadParams: {required: ['script', 'args']}, + }, }, '/session/:sessionId/screenshot': { - GET: {command: 'getScreenshot'} + GET: {command: 'getScreenshot'}, }, '/session/:sessionId/ime/available_engines': { - GET: {command: 'availableIMEEngines'} + GET: {command: 'availableIMEEngines'}, }, '/session/:sessionId/ime/active_engine': { - GET: {command: 'getActiveIMEEngine'} + GET: {command: 'getActiveIMEEngine'}, }, '/session/:sessionId/ime/activated': { - GET: {command: 'isIMEActivated'} + GET: {command: 'isIMEActivated'}, }, '/session/:sessionId/ime/deactivate': { - POST: {command: 'deactivateIMEEngine'} + POST: {command: 'deactivateIMEEngine'}, }, '/session/:sessionId/ime/activate': { - POST: {command: 'activateIMEEngine', payloadParams: {required: ['engine']}} + POST: {command: 'activateIMEEngine', payloadParams: {required: ['engine']}}, }, '/session/:sessionId/frame': { - POST: {command: 'setFrame', payloadParams: {required: ['id']}} + POST: {command: 'setFrame', payloadParams: {required: ['id']}}, }, '/session/:sessionId/frame/parent': { - POST: {} + POST: {}, }, '/session/:sessionId/window': { GET: {command: 'getWindowHandle'}, - POST: {command: 'setWindow', payloadParams: { - optional: ['name', 'handle'], - // Return both values to match W3C and JSONWP protocols - makeArgs: (jsonObj) => { - if (util.hasValue(jsonObj.handle) && !util.hasValue(jsonObj.name)) { - return [jsonObj.handle, jsonObj.handle]; - } - if (util.hasValue(jsonObj.name) && !util.hasValue(jsonObj.handle)) { - return [jsonObj.name, jsonObj.name]; - } - return [jsonObj.name, jsonObj.handle]; + POST: { + command: 'setWindow', + payloadParams: { + optional: ['name', 'handle'], + // Return both values to match W3C and JSONWP protocols + makeArgs: (jsonObj) => { + if (util.hasValue(jsonObj.handle) && !util.hasValue(jsonObj.name)) { + return [jsonObj.handle, jsonObj.handle]; + } + if (util.hasValue(jsonObj.name) && !util.hasValue(jsonObj.handle)) { + return [jsonObj.name, jsonObj.name]; + } + return [jsonObj.name, jsonObj.handle]; + }, + validate: (jsonObj) => + !util.hasValue(jsonObj.name) && + !util.hasValue(jsonObj.handle) && + 'we require one of "name" or "handle" to be set', }, - validate: (jsonObj) => (!util.hasValue(jsonObj.name) && !util.hasValue(jsonObj.handle)) - && 'we require one of "name" or "handle" to be set', - }}, - DELETE: {command: 'closeWindow'} + }, + DELETE: {command: 'closeWindow'}, }, '/session/:sessionId/window/:windowhandle/size': { GET: {command: 'getWindowSize'}, - POST: {} + POST: {}, }, '/session/:sessionId/window/:windowhandle/position': { POST: {}, - GET: {} + GET: {}, }, '/session/:sessionId/window/:windowhandle/maximize': { - POST: {command: 'maximizeWindow'} + POST: {command: 'maximizeWindow'}, }, '/session/:sessionId/cookie': { GET: {command: 'getCookies'}, POST: {command: 'setCookie', payloadParams: {required: ['cookie']}}, - DELETE: {command: 'deleteCookies'} + DELETE: {command: 'deleteCookies'}, }, '/session/:sessionId/cookie/:name': { GET: {command: 'getCookie'}, - DELETE: {command: 'deleteCookie'} + DELETE: {command: 'deleteCookie'}, }, '/session/:sessionId/source': { - GET: {command: 'getPageSource'} + GET: {command: 'getPageSource'}, }, '/session/:sessionId/title': { - GET: {command: 'title'} + GET: {command: 'title'}, }, '/session/:sessionId/element': { - POST: {command: 'findElement', payloadParams: {required: ['using', 'value']}} + POST: { + command: 'findElement', + payloadParams: {required: ['using', 'value']}, + }, }, '/session/:sessionId/elements': { - POST: {command: 'findElements', payloadParams: {required: ['using', 'value']}} + POST: { + command: 'findElements', + payloadParams: {required: ['using', 'value']}, + }, }, '/session/:sessionId/element/active': { GET: {command: 'active'}, // W3C: https://w3c.github.io/webdriver/webdriver-spec.html#dfn-get-active-element - POST: {command: 'active'} + POST: {command: 'active'}, }, '/session/:sessionId/element/:elementId': { - GET: {} + GET: {}, }, '/session/:sessionId/element/:elementId/element': { - POST: {command: 'findElementFromElement', payloadParams: {required: ['using', 'value']}} + POST: { + command: 'findElementFromElement', + payloadParams: {required: ['using', 'value']}, + }, }, '/session/:sessionId/element/:elementId/elements': { - POST: {command: 'findElementsFromElement', payloadParams: {required: ['using', 'value']}} + POST: { + command: 'findElementsFromElement', + payloadParams: {required: ['using', 'value']}, + }, }, '/session/:sessionId/element/:elementId/click': { - POST: {command: 'click'} + POST: {command: 'click'}, }, '/session/:sessionId/element/:elementId/submit': { - POST: {command: 'submit'} + POST: {command: 'submit'}, }, '/session/:sessionId/element/:elementId/text': { - GET: {command: 'getText'} + GET: {command: 'getText'}, }, '/session/:sessionId/element/:elementId/value': { POST: { command: 'setValue', payloadParams: { - validate: (jsonObj) => (!util.hasValue(jsonObj.value) && !util.hasValue(jsonObj.text)) && - 'we require one of "text" or "value" params', + validate: (jsonObj) => + !util.hasValue(jsonObj.value) && + !util.hasValue(jsonObj.text) && + 'we require one of "text" or "value" params', optional: ['value', 'text'], // override the default argument constructor because of the special // logic here. Basically we want to accept either a value (old JSONWP) @@ -204,151 +241,168 @@ const METHOD_MAP = { // command (not both). Prefer 'value' since it's more // backward-compatible. makeArgs: (jsonObj) => [jsonObj.value || jsonObj.text], - } - } + }, + }, }, '/session/:sessionId/keys': { - POST: {command: 'keys', payloadParams: {required: ['value']}} + POST: {command: 'keys', payloadParams: {required: ['value']}}, }, '/session/:sessionId/element/:elementId/name': { - GET: {command: 'getName'} + GET: {command: 'getName'}, }, '/session/:sessionId/element/:elementId/clear': { - POST: {command: 'clear'} + POST: {command: 'clear'}, }, '/session/:sessionId/element/:elementId/selected': { - GET: {command: 'elementSelected'} + GET: {command: 'elementSelected'}, }, '/session/:sessionId/element/:elementId/enabled': { - GET: {command: 'elementEnabled'} + GET: {command: 'elementEnabled'}, }, '/session/:sessionId/element/:elementId/attribute/:name': { - GET: {command: 'getAttribute'} + GET: {command: 'getAttribute'}, }, '/session/:sessionId/element/:elementId/equals/:otherId': { - GET: {command: 'equalsElement'} + GET: {command: 'equalsElement'}, }, '/session/:sessionId/element/:elementId/displayed': { - GET: {command: 'elementDisplayed'} + GET: {command: 'elementDisplayed'}, }, '/session/:sessionId/element/:elementId/location': { - GET: {command: 'getLocation'} + GET: {command: 'getLocation'}, }, '/session/:sessionId/element/:elementId/location_in_view': { - GET: {command: 'getLocationInView'} + GET: {command: 'getLocationInView'}, }, '/session/:sessionId/element/:elementId/size': { - GET: {command: 'getSize'} + GET: {command: 'getSize'}, }, '/session/:sessionId/element/:elementId/shadow': { - GET: {command: 'elementShadowRoot'} + GET: {command: 'elementShadowRoot'}, }, '/session/:sessionId/shadow/:shadowId/element': { - POST: {command: 'findElementFromShadowRoot', payloadParams: {required: ['using', 'value']}} + POST: { + command: 'findElementFromShadowRoot', + payloadParams: {required: ['using', 'value']}, + }, }, '/session/:sessionId/shadow/:shadowId/elements': { - POST: {command: 'findElementsFromShadowRoot', payloadParams: {required: ['using', 'value']}} + POST: { + command: 'findElementsFromShadowRoot', + payloadParams: {required: ['using', 'value']}, + }, }, '/session/:sessionId/element/:elementId/css/:propertyName': { - GET: {command: 'getCssProperty'} + GET: {command: 'getCssProperty'}, }, '/session/:sessionId/orientation': { GET: {command: 'getOrientation'}, - POST: {command: 'setOrientation', payloadParams: {required: ['orientation']}} + POST: { + command: 'setOrientation', + payloadParams: {required: ['orientation']}, + }, }, '/session/:sessionId/rotation': { GET: {command: 'getRotation'}, - POST: {command: 'setRotation', payloadParams: {required: ['x', 'y', 'z']}} + POST: {command: 'setRotation', payloadParams: {required: ['x', 'y', 'z']}}, }, '/session/:sessionId/moveto': { - POST: {command: 'moveTo', payloadParams: {optional: ['element', 'xoffset', 'yoffset']}} + POST: { + command: 'moveTo', + payloadParams: {optional: ['element', 'xoffset', 'yoffset']}, + }, }, '/session/:sessionId/click': { - POST: {command: 'clickCurrent', payloadParams: {optional: ['button']}} + POST: {command: 'clickCurrent', payloadParams: {optional: ['button']}}, }, '/session/:sessionId/buttondown': { - POST: {command: 'buttonDown', payloadParams: {optional: ['button']}} + POST: {command: 'buttonDown', payloadParams: {optional: ['button']}}, }, '/session/:sessionId/buttonup': { - POST: {command: 'buttonUp', payloadParams: {optional: ['button']}} + POST: {command: 'buttonUp', payloadParams: {optional: ['button']}}, }, '/session/:sessionId/doubleclick': { - POST: {command: 'doubleClick'} + POST: {command: 'doubleClick'}, }, '/session/:sessionId/touch/click': { - POST: {command: 'click', payloadParams: {required: ['element']}} + POST: {command: 'click', payloadParams: {required: ['element']}}, }, '/session/:sessionId/touch/down': { - POST: {command: 'touchDown', payloadParams: {required: ['x', 'y']}} + POST: {command: 'touchDown', payloadParams: {required: ['x', 'y']}}, }, '/session/:sessionId/touch/up': { - POST: {command: 'touchUp', payloadParams: {required: ['x', 'y']}} + POST: {command: 'touchUp', payloadParams: {required: ['x', 'y']}}, }, '/session/:sessionId/touch/move': { - POST: {command: 'touchMove', payloadParams: {required: ['x', 'y']}} + POST: {command: 'touchMove', payloadParams: {required: ['x', 'y']}}, }, '/session/:sessionId/touch/scroll': { - POST: {} + POST: {}, }, '/session/:sessionId/touch/doubleclick': { - POST: {} + POST: {}, }, '/session/:sessionId/actions': { POST: {command: 'performActions', payloadParams: {required: ['actions']}}, - DELETE: {command: 'releaseActions'} + DELETE: {command: 'releaseActions'}, }, '/session/:sessionId/touch/longclick': { - POST: {command: 'touchLongClick', payloadParams: {required: ['elements']}} + POST: {command: 'touchLongClick', payloadParams: {required: ['elements']}}, }, '/session/:sessionId/touch/flick': { - POST: {command: 'flick', payloadParams: {optional: ['element', 'xspeed', 'yspeed', 'xoffset', 'yoffset', 'speed']}} + POST: { + command: 'flick', + payloadParams: { + optional: ['element', 'xspeed', 'yspeed', 'xoffset', 'yoffset', 'speed'], + }, + }, }, '/session/:sessionId/location': { GET: {command: 'getGeoLocation'}, - POST: {command: 'setGeoLocation', payloadParams: {required: ['location']}} + POST: {command: 'setGeoLocation', payloadParams: {required: ['location']}}, }, '/session/:sessionId/local_storage': { GET: {}, POST: {}, - DELETE: {} + DELETE: {}, }, '/session/:sessionId/local_storage/key/:key': { GET: {}, - DELETE: {} + DELETE: {}, }, '/session/:sessionId/local_storage/size': { - GET: {} + GET: {}, }, '/session/:sessionId/session_storage': { GET: {}, POST: {}, - DELETE: {} + DELETE: {}, }, '/session/:sessionId/session_storage/key/:key': { GET: {}, - DELETE: {} + DELETE: {}, }, '/session/:sessionId/session_storage/size': { - GET: {} + GET: {}, }, // Selenium 4 clients '/session/:sessionId/se/log': { - POST: {command: 'getLog', payloadParams: {required: ['type']}} + POST: {command: 'getLog', payloadParams: {required: ['type']}}, }, // Selenium 4 clients '/session/:sessionId/se/log/types': { - GET: {command: 'getLogTypes'} + GET: {command: 'getLogTypes'}, }, // mjsonwire, appium clients '/session/:sessionId/log': { - POST: {command: 'getLog', payloadParams: {required: ['type']}} + POST: {command: 'getLog', payloadParams: {required: ['type']}}, }, // mjsonwire, appium clients '/session/:sessionId/log/types': { - GET: {command: 'getLogTypes'} + GET: {command: 'getLogTypes'}, }, '/session/:sessionId/application_cache/status': { - GET: {} + GET: {}, }, // @@ -356,107 +410,157 @@ const METHOD_MAP = { // '/session/:sessionId/context': { GET: {command: 'getCurrentContext'}, - POST: {command: 'setContext', payloadParams: {required: ['name']}} + POST: {command: 'setContext', payloadParams: {required: ['name']}}, }, '/session/:sessionId/contexts': { - GET: {command: 'getContexts'} + GET: {command: 'getContexts'}, }, '/session/:sessionId/element/:elementId/pageIndex': { - GET: {command: 'getPageIndex'} + GET: {command: 'getPageIndex'}, }, '/session/:sessionId/network_connection': { GET: {command: 'getNetworkConnection'}, - POST: {command: 'setNetworkConnection', payloadParams: {unwrap: 'parameters', required: ['type']}} + POST: { + command: 'setNetworkConnection', + payloadParams: {unwrap: 'parameters', required: ['type']}, + }, }, '/session/:sessionId/touch/perform': { - POST: {command: 'performTouch', payloadParams: {wrap: 'actions', required: ['actions']}} + POST: { + command: 'performTouch', + payloadParams: {wrap: 'actions', required: ['actions']}, + }, }, '/session/:sessionId/touch/multi/perform': { - POST: {command: 'performMultiAction', payloadParams: {required: ['actions'], optional: ['elementId']}} + POST: { + command: 'performMultiAction', + payloadParams: {required: ['actions'], optional: ['elementId']}, + }, }, '/session/:sessionId/receive_async_response': { - POST: {command: 'receiveAsyncResponse', payloadParams: {required: ['status', 'value']}} + POST: { + command: 'receiveAsyncResponse', + payloadParams: {required: ['status', 'value']}, + }, }, '/session/:sessionId/appium/device/shake': { - POST: {command: 'mobileShake'} + POST: {command: 'mobileShake'}, }, '/session/:sessionId/appium/device/system_time': { GET: {command: 'getDeviceTime', payloadParams: {optional: ['format']}}, - POST: {command: 'getDeviceTime', payloadParams: {optional: ['format']}} + POST: {command: 'getDeviceTime', payloadParams: {optional: ['format']}}, }, '/session/:sessionId/appium/device/lock': { - POST: {command: 'lock', payloadParams: {optional: ['seconds']}} + POST: {command: 'lock', payloadParams: {optional: ['seconds']}}, }, '/session/:sessionId/appium/device/unlock': { - POST: {command: 'unlock'} + POST: {command: 'unlock'}, }, '/session/:sessionId/appium/device/is_locked': { - POST: {command: 'isLocked'} + POST: {command: 'isLocked'}, }, '/session/:sessionId/appium/start_recording_screen': { - POST: {command: 'startRecordingScreen', payloadParams: {optional: ['options']}} + POST: { + command: 'startRecordingScreen', + payloadParams: {optional: ['options']}, + }, }, '/session/:sessionId/appium/stop_recording_screen': { - POST: {command: 'stopRecordingScreen', payloadParams: {optional: ['options']}} + POST: { + command: 'stopRecordingScreen', + payloadParams: {optional: ['options']}, + }, }, '/session/:sessionId/appium/performanceData/types': { - POST: {command: 'getPerformanceDataTypes'} + POST: {command: 'getPerformanceDataTypes'}, }, '/session/:sessionId/appium/getPerformanceData': { - POST: {command: 'getPerformanceData', payloadParams: {required: ['packageName', 'dataType'], optional: ['dataReadTimeout']}} + POST: { + command: 'getPerformanceData', + payloadParams: { + required: ['packageName', 'dataType'], + optional: ['dataReadTimeout'], + }, + }, }, '/session/:sessionId/appium/device/press_keycode': { - POST: {command: 'pressKeyCode', payloadParams: {required: ['keycode'], optional: ['metastate', 'flags']}} + POST: { + command: 'pressKeyCode', + payloadParams: {required: ['keycode'], optional: ['metastate', 'flags']}, + }, }, '/session/:sessionId/appium/device/long_press_keycode': { - POST: {command: 'longPressKeyCode', payloadParams: {required: ['keycode'], optional: ['metastate', 'flags']}} + POST: { + command: 'longPressKeyCode', + payloadParams: {required: ['keycode'], optional: ['metastate', 'flags']}, + }, }, '/session/:sessionId/appium/device/finger_print': { - POST: {command: 'fingerprint', payloadParams: {required: ['fingerprintId']}} + POST: { + command: 'fingerprint', + payloadParams: {required: ['fingerprintId']}, + }, }, '/session/:sessionId/appium/device/send_sms': { - POST: {command: 'sendSMS', payloadParams: {required: ['phoneNumber', 'message']}} + POST: { + command: 'sendSMS', + payloadParams: {required: ['phoneNumber', 'message']}, + }, }, '/session/:sessionId/appium/device/gsm_call': { - POST: {command: 'gsmCall', payloadParams: {required: ['phoneNumber', 'action']}} + POST: { + command: 'gsmCall', + payloadParams: {required: ['phoneNumber', 'action']}, + }, }, '/session/:sessionId/appium/device/gsm_signal': { POST: { command: 'gsmSignal', payloadParams: { - validate: (jsonObj) => (!util.hasValue(jsonObj.signalStrength) && !util.hasValue(jsonObj.signalStrengh)) && - 'we require one of "signalStrength" or "signalStrengh" params', + validate: (jsonObj) => + !util.hasValue(jsonObj.signalStrength) && + !util.hasValue(jsonObj.signalStrengh) && + 'we require one of "signalStrength" or "signalStrengh" params', optional: ['signalStrength', 'signalStrengh'], // backward-compatible. sonObj.signalStrength can be 0 - makeArgs: (jsonObj) => [util.hasValue(jsonObj.signalStrength) ? jsonObj.signalStrength : jsonObj.signalStrengh] - } - } + makeArgs: (jsonObj) => [ + util.hasValue(jsonObj.signalStrength) ? jsonObj.signalStrength : jsonObj.signalStrengh, + ], + }, + }, }, '/session/:sessionId/appium/device/gsm_voice': { - POST: {command: 'gsmVoice', payloadParams: {required: ['state']}} + POST: {command: 'gsmVoice', payloadParams: {required: ['state']}}, }, '/session/:sessionId/appium/device/power_capacity': { - POST: {command: 'powerCapacity', payloadParams: {required: ['percent']}} + POST: {command: 'powerCapacity', payloadParams: {required: ['percent']}}, }, '/session/:sessionId/appium/device/power_ac': { - POST: {command: 'powerAC', payloadParams: {required: ['state']}} + POST: {command: 'powerAC', payloadParams: {required: ['state']}}, }, '/session/:sessionId/appium/device/network_speed': { - POST: {command: 'networkSpeed', payloadParams: {required: ['netspeed']}} + POST: {command: 'networkSpeed', payloadParams: {required: ['netspeed']}}, }, '/session/:sessionId/appium/device/keyevent': { - POST: {command: 'keyevent', payloadParams: {required: ['keycode'], optional: ['metastate']}} + POST: { + command: 'keyevent', + payloadParams: {required: ['keycode'], optional: ['metastate']}, + }, }, '/session/:sessionId/appium/device/rotate': { - POST: {command: 'mobileRotation', payloadParams: { - required: ['x', 'y', 'radius', 'rotation', 'touchCount', 'duration'], - optional: ['element'] }} + POST: { + command: 'mobileRotation', + payloadParams: { + required: ['x', 'y', 'radius', 'rotation', 'touchCount', 'duration'], + optional: ['element'], + }, + }, }, '/session/:sessionId/appium/device/current_activity': { - GET: {command: 'getCurrentActivity'} + GET: {command: 'getCurrentActivity'}, }, '/session/:sessionId/appium/device/current_package': { - GET: {command: 'getCurrentPackage'} + GET: {command: 'getCurrentPackage'}, }, //region Applications Management '/session/:sessionId/appium/device/install_app': { @@ -464,164 +568,199 @@ const METHOD_MAP = { command: 'installApp', payloadParams: { required: ['appPath'], - optional: ['options'] - } - } + optional: ['options'], + }, + }, }, '/session/:sessionId/appium/device/activate_app': { POST: { command: 'activateApp', payloadParams: { required: [['appId'], ['bundleId']], - optional: ['options'] - } - } + optional: ['options'], + }, + }, }, '/session/:sessionId/appium/device/remove_app': { POST: { command: 'removeApp', payloadParams: { required: [['appId'], ['bundleId']], - optional: ['options'] - } - } + optional: ['options'], + }, + }, }, '/session/:sessionId/appium/device/terminate_app': { POST: { command: 'terminateApp', payloadParams: { required: [['appId'], ['bundleId']], - optional: ['options'] - } - } + optional: ['options'], + }, + }, }, '/session/:sessionId/appium/device/app_installed': { POST: { command: 'isAppInstalled', payloadParams: { - required: [['appId'], ['bundleId']] - } - } + required: [['appId'], ['bundleId']], + }, + }, }, '/session/:sessionId/appium/device/app_state': { GET: { command: 'queryAppState', payloadParams: { - required: [['appId'], ['bundleId']] - } + required: [['appId'], ['bundleId']], + }, }, POST: { command: 'queryAppState', payloadParams: { - required: [['appId'], ['bundleId']] - } - } + required: [['appId'], ['bundleId']], + }, + }, }, //endregion '/session/:sessionId/appium/device/hide_keyboard': { - POST: {command: 'hideKeyboard', payloadParams: {optional: ['strategy', 'key', 'keyCode', 'keyName']}} + POST: { + command: 'hideKeyboard', + payloadParams: {optional: ['strategy', 'key', 'keyCode', 'keyName']}, + }, }, '/session/:sessionId/appium/device/is_keyboard_shown': { - GET: {command: 'isKeyboardShown'} + GET: {command: 'isKeyboardShown'}, }, '/session/:sessionId/appium/device/push_file': { - POST: {command: 'pushFile', payloadParams: {required: ['path', 'data']}} + POST: {command: 'pushFile', payloadParams: {required: ['path', 'data']}}, }, '/session/:sessionId/appium/device/pull_file': { - POST: {command: 'pullFile', payloadParams: {required: ['path']}} + POST: {command: 'pullFile', payloadParams: {required: ['path']}}, }, '/session/:sessionId/appium/device/pull_folder': { - POST: {command: 'pullFolder', payloadParams: {required: ['path']}} + POST: {command: 'pullFolder', payloadParams: {required: ['path']}}, }, '/session/:sessionId/appium/device/toggle_airplane_mode': { - POST: {command: 'toggleFlightMode'} + POST: {command: 'toggleFlightMode'}, }, '/session/:sessionId/appium/device/toggle_data': { - POST: {command: 'toggleData'} + POST: {command: 'toggleData'}, }, '/session/:sessionId/appium/device/toggle_wifi': { - POST: {command: 'toggleWiFi'} + POST: {command: 'toggleWiFi'}, }, '/session/:sessionId/appium/device/toggle_location_services': { - POST: {command: 'toggleLocationServices'} + POST: {command: 'toggleLocationServices'}, }, '/session/:sessionId/appium/device/open_notifications': { - POST: {command: 'openNotifications'} + POST: {command: 'openNotifications'}, }, '/session/:sessionId/appium/device/start_activity': { POST: { command: 'startActivity', payloadParams: { required: ['appPackage', 'appActivity'], - optional: ['appWaitPackage', 'appWaitActivity', 'intentAction', - 'intentCategory', 'intentFlags', 'optionalIntentArguments', 'dontStopAppOnReset'] - } - } + optional: [ + 'appWaitPackage', + 'appWaitActivity', + 'intentAction', + 'intentCategory', + 'intentFlags', + 'optionalIntentArguments', + 'dontStopAppOnReset', + ], + }, + }, }, '/session/:sessionId/appium/device/system_bars': { - GET: {command: 'getSystemBars'} + GET: {command: 'getSystemBars'}, }, '/session/:sessionId/appium/device/display_density': { - GET: {command: 'getDisplayDensity'} + GET: {command: 'getDisplayDensity'}, }, '/session/:sessionId/appium/simulator/touch_id': { - POST: {command: 'touchId', payloadParams: {required: ['match']}} + POST: {command: 'touchId', payloadParams: {required: ['match']}}, }, '/session/:sessionId/appium/simulator/toggle_touch_id_enrollment': { - POST: {command: 'toggleEnrollTouchId', payloadParams: {optional: ['enabled']}} + POST: { + command: 'toggleEnrollTouchId', + payloadParams: {optional: ['enabled']}, + }, }, '/session/:sessionId/appium/app/launch': { - POST: {command: 'launchApp'} + POST: {command: 'launchApp'}, }, '/session/:sessionId/appium/app/close': { - POST: {command: 'closeApp'} + POST: {command: 'closeApp'}, }, '/session/:sessionId/appium/app/reset': { - POST: {command: 'reset'} + POST: {command: 'reset'}, }, '/session/:sessionId/appium/app/background': { - POST: {command: 'background', payloadParams: {required: ['seconds']}} + POST: {command: 'background', payloadParams: {required: ['seconds']}}, }, '/session/:sessionId/appium/app/end_test_coverage': { - POST: {command: 'endCoverage', payloadParams: {required: ['intent', 'path']}} + POST: { + command: 'endCoverage', + payloadParams: {required: ['intent', 'path']}, + }, }, '/session/:sessionId/appium/app/strings': { - POST: {command: 'getStrings', payloadParams: {optional: ['language', 'stringFile']}} + POST: { + command: 'getStrings', + payloadParams: {optional: ['language', 'stringFile']}, + }, }, '/session/:sessionId/appium/element/:elementId/value': { - POST: {command: 'setValueImmediate', payloadParams: { - validate: (jsonObj) => (!util.hasValue(jsonObj.value) && !util.hasValue(jsonObj.text)) && + POST: { + command: 'setValueImmediate', + payloadParams: { + validate: (jsonObj) => + !util.hasValue(jsonObj.value) && + !util.hasValue(jsonObj.text) && 'we require one of "text" or "value" params', - optional: ['value', 'text'], - // We want to either a value (old JSONWP) or a text (new W3C) parameter, - // but only send one of them to the command (not both). - // Prefer 'value' since it's more backward-compatible. - makeArgs: (jsonObj) => [jsonObj.value || jsonObj.text], - }} + optional: ['value', 'text'], + // We want to either a value (old JSONWP) or a text (new W3C) parameter, + // but only send one of them to the command (not both). + // Prefer 'value' since it's more backward-compatible. + makeArgs: (jsonObj) => [jsonObj.value || jsonObj.text], + }, + }, }, '/session/:sessionId/appium/element/:elementId/replace_value': { - POST: {command: 'replaceValue', payloadParams: { - validate: (jsonObj) => (!util.hasValue(jsonObj.value) && !util.hasValue(jsonObj.text)) && + POST: { + command: 'replaceValue', + payloadParams: { + validate: (jsonObj) => + !util.hasValue(jsonObj.value) && + !util.hasValue(jsonObj.text) && 'we require one of "text" or "value" params', - optional: ['value', 'text'], - // We want to either a value (old JSONWP) or a text (new W3C) parameter, - // but only send one of them to the command (not both). - // Prefer 'value' since it's more backward-compatible. - makeArgs: (jsonObj) => [jsonObj.value ?? jsonObj.text ?? ''], - }} + optional: ['value', 'text'], + // We want to either a value (old JSONWP) or a text (new W3C) parameter, + // but only send one of them to the command (not both). + // Prefer 'value' since it's more backward-compatible. + makeArgs: (jsonObj) => [jsonObj.value ?? jsonObj.text ?? ''], + }, + }, }, '/session/:sessionId/appium/settings': { POST: {command: 'updateSettings', payloadParams: {required: ['settings']}}, - GET: {command: 'getSettings'} + GET: {command: 'getSettings'}, }, '/session/:sessionId/appium/receive_async_response': { - POST: {command: 'receiveAsyncResponse', payloadParams: {required: ['response']}} + POST: { + command: 'receiveAsyncResponse', + payloadParams: {required: ['response']}, + }, }, '/session/:sessionId/appium/events': { - POST: {command: 'getLogEvents', payloadParams: {optional: ['type']}} + POST: {command: 'getLogEvents', payloadParams: {optional: ['type']}}, }, '/session/:sessionId/appium/log_event': { - POST: {command: 'logCustomEvent', payloadParams: {required: ['vendor', 'event']}} + POST: { + command: 'logCustomEvent', + payloadParams: {required: ['vendor', 'event']}, + }, }, /* @@ -636,15 +775,15 @@ const METHOD_MAP = { POST: { command: 'setAlertText', payloadParams: SET_ALERT_TEXT_PAYLOAD_PARAMS, - } + }, }, // MJSONWP '/session/:sessionId/accept_alert': { - POST: {command: 'postAcceptAlert'} + POST: {command: 'postAcceptAlert'}, }, // MJSONWP '/session/:sessionId/dismiss_alert': { - POST: {command: 'postDismissAlert'} + POST: {command: 'postDismissAlert'}, }, // https://w3c.github.io/webdriver/webdriver-spec.html#user-prompts '/session/:sessionId/alert/text': { @@ -652,77 +791,78 @@ const METHOD_MAP = { POST: { command: 'setAlertText', payloadParams: SET_ALERT_TEXT_PAYLOAD_PARAMS, - } + }, }, '/session/:sessionId/alert/accept': { - POST: {command: 'postAcceptAlert'} + POST: {command: 'postAcceptAlert'}, }, '/session/:sessionId/alert/dismiss': { - POST: {command: 'postDismissAlert'} + POST: {command: 'postDismissAlert'}, }, // https://w3c.github.io/webdriver/webdriver-spec.html#get-element-rect '/session/:sessionId/element/:elementId/rect': { - GET: {command: 'getElementRect'} + GET: {command: 'getElementRect'}, }, '/session/:sessionId/execute/sync': { - POST: {command: 'execute', payloadParams: {required: ['script', 'args']}} + POST: {command: 'execute', payloadParams: {required: ['script', 'args']}}, }, '/session/:sessionId/execute/async': { - POST: {command: 'executeAsync', payloadParams: {required: ['script', 'args']}} + POST: { + command: 'executeAsync', + payloadParams: {required: ['script', 'args']}, + }, }, // Pre-W3C endpoint for element screenshot '/session/:sessionId/screenshot/:elementId': { - GET: {command: 'getElementScreenshot'} + GET: {command: 'getElementScreenshot'}, }, '/session/:sessionId/element/:elementId/screenshot': { - GET: {command: 'getElementScreenshot'} + GET: {command: 'getElementScreenshot'}, }, '/session/:sessionId/window/rect': { GET: {command: 'getWindowRect'}, - POST: {command: 'setWindowRect', payloadParams: {required: ['x', 'y', 'width', 'height']}}, + POST: { + command: 'setWindowRect', + payloadParams: {required: ['x', 'y', 'width', 'height']}, + }, }, '/session/:sessionId/window/maximize': { - POST: {command: 'maximizeWindow'} + POST: {command: 'maximizeWindow'}, }, '/session/:sessionId/window/minimize': { - POST: {command: 'minimizeWindow'} + POST: {command: 'minimizeWindow'}, }, '/session/:sessionId/window/fullscreen': { - POST: {command: 'fullScreenWindow'} + POST: {command: 'fullScreenWindow'}, }, '/session/:sessionId/window/new': { - POST: {command: 'createNewWindow', payloadParams: {optional: ['type']}} + POST: {command: 'createNewWindow', payloadParams: {optional: ['type']}}, }, '/session/:sessionId/element/:elementId/property/:name': { - GET: {command: 'getProperty'} + GET: {command: 'getProperty'}, }, '/session/:sessionId/appium/device/set_clipboard': { POST: { command: 'setClipboard', payloadParams: { required: ['content'], - optional: [ - 'contentType', - 'label', - ] + optional: ['contentType', 'label'], }, - } + }, }, '/session/:sessionId/appium/device/get_clipboard': { POST: { command: 'getClipboard', payloadParams: { - optional: [ - 'contentType', - ] + optional: ['contentType'], }, - } + }, }, // chromium devtools // https://chromium.googlesource.com/chromium/src/+/master/chrome/test/chromedriver/server/http_handler.cc '/session/:sessionId/:vendor/cdp/execute': { - POST: {command: 'executeCdp', payloadParams: {required: ['cmd', 'params']}} + POST: {command: 'executeCdp', payloadParams: {required: ['cmd', 'params']}}, }, //region Webauthn @@ -734,14 +874,14 @@ const METHOD_MAP = { payloadParams: { required: ['protocol', 'transport'], optional: ['hasResidentKey', 'hasUserVerification', 'isUserConsenting', 'isUserVerified'], - } - } + }, + }, }, '/session/:sessionId/webauthn/authenticator/:authenticatorId': { DELETE: { - command: 'removeVirtualAuthenticator' - } + command: 'removeVirtualAuthenticator', + }, }, '/session/:sessionId/webauthn/authenticator/:authenticatorId/credential': { @@ -750,8 +890,8 @@ const METHOD_MAP = { payloadParams: { required: ['credentialId', 'isResidentCredential', 'rpId', 'privateKey'], optional: ['userHandle', 'signCount'], - } - } + }, + }, }, '/session/:sessionId/webauthn/authenticator/:authenticatorId/credentials': { @@ -760,20 +900,19 @@ const METHOD_MAP = { }, '/session/:sessionId/webauthn/authenticator/:authenticatorId/credentials/:credentialId': { - DELETE: {command: 'removeAuthCredential'} + DELETE: {command: 'removeAuthCredential'}, }, '/session/:sessionId/webauthn/authenticator/:authenticatorId/uv': { POST: { command: 'setUserAuthVerified', payloadParams: { - required: ['isUserVerified'] - } - } + required: ['isUserVerified'], + }, + }, }, //endregion - }; // driver command names @@ -790,7 +929,7 @@ const RE_ESCAPE = /[-[\]{}()+?.,\\^$|#\s]/g; const RE_PARAM = /([:*])(\w+)/g; class Route { - constructor (route) { + constructor(route) { this.paramNames = []; let reStr = route.replace(RE_ESCAPE, '\\$&'); @@ -801,7 +940,7 @@ class Route { this.routeRegexp = new RegExp(`^${reStr}$`); } - parse (url) { + parse(url) { //if (url.indexOf('timeouts') !== -1 && this.routeRegexp.toString().indexOf('timeouts') !== -1) { //debugger; //} @@ -817,7 +956,7 @@ class Route { } } -function routeToCommandName (endpoint, method, basePath = DEFAULT_BASE_PATH) { +function routeToCommandName(endpoint, method, basePath = DEFAULT_BASE_PATH) { let dstRoute = null; // remove any query string @@ -825,14 +964,17 @@ function routeToCommandName (endpoint, method, basePath = DEFAULT_BASE_PATH) { endpoint = endpoint.slice(0, endpoint.indexOf('?')); } - const actualEndpoint = endpoint === '/' ? '' : - (_.startsWith(endpoint, '/') ? endpoint : `/${endpoint}`); + const actualEndpoint = + endpoint === '/' ? '' : _.startsWith(endpoint, '/') ? endpoint : `/${endpoint}`; for (let currentRoute of _.keys(METHOD_MAP)) { const route = new Route(`${basePath}${currentRoute}`); // we don't care about the actual session id for matching - if (route.parse(`${basePath}/session/ignored-session-id${actualEndpoint}`) || - route.parse(`${basePath}${actualEndpoint}`) || route.parse(actualEndpoint)) { + if ( + route.parse(`${basePath}/session/ignored-session-id${actualEndpoint}`) || + route.parse(`${basePath}${actualEndpoint}`) || + route.parse(actualEndpoint) + ) { dstRoute = currentRoute; break; } @@ -852,4 +994,4 @@ function routeToCommandName (endpoint, method, basePath = DEFAULT_BASE_PATH) { // driver commands that do not require a session to already exist const NO_SESSION_ID_COMMANDS = ['createSession', 'getStatus', 'getSessions']; -export { METHOD_MAP, ALL_COMMANDS, NO_SESSION_ID_COMMANDS, routeToCommandName }; +export {METHOD_MAP, ALL_COMMANDS, NO_SESSION_ID_COMMANDS, routeToCommandName}; diff --git a/packages/base-driver/lib/protocol/validators.js b/packages/base-driver/lib/protocol/validators.js index 429e46818..3657a15c8 100644 --- a/packages/base-driver/lib/protocol/validators.js +++ b/packages/base-driver/lib/protocol/validators.js @@ -1,10 +1,10 @@ import _ from 'lodash'; -function isNumber (o) { +function isNumber(o) { return _.isNumber(o) || !_.isNaN(parseInt(o, 10)) || !_.isNaN(parseFloat(o)); } -function msValidator (ms) { +function msValidator(ms) { if (!_.isNumber(ms) || ms < 0) { throw new Error('Wait ms must be a number equal to 0 or greater'); } @@ -24,7 +24,7 @@ const validators = { msValidator(ms); }, clickCurrent: (button) => { - if (!(isNumber(button) || _.isUndefined(button)) || (button < 0 || button > 2)) { + if (!(isNumber(button) || _.isUndefined(button)) || button < 0 || button > 2) { throw new Error('Click button must be 0, 1, or 2'); } }, @@ -32,7 +32,7 @@ const validators = { if (!isNumber(type) || [0, 1, 2, 4, 6].indexOf(type) === -1) { throw new Error('Network type must be one of 0, 1, 2, 4, 6'); } - } + }, }; -export { validators }; +export {validators}; diff --git a/packages/base-driver/test/basedriver/driver-e2e-tests.js b/packages/base-driver/test/basedriver/driver-e2e-tests.js index 4be041f95..447084365 100644 --- a/packages/base-driver/test/basedriver/driver-e2e-tests.js +++ b/packages/base-driver/test/basedriver/driver-e2e-tests.js @@ -1,12 +1,12 @@ import _ from 'lodash'; -import { BaseDriver, server, routeConfiguringFunction, DeviceSettings } from '../../lib'; +import {BaseDriver, server, routeConfiguringFunction, DeviceSettings} from '../../lib'; import axios from 'axios'; import B from 'bluebird'; -import { TEST_HOST, getTestPort, createAppiumURL, METHODS } from '../helpers'; -import { PREFIXED_APPIUM_OPTS_CAP } from '../../lib/basedriver/capabilities'; +import {TEST_HOST, getTestPort, createAppiumURL, METHODS} from '../helpers'; +import {PREFIXED_APPIUM_OPTS_CAP} from '../../lib/basedriver/capabilities'; const {POST, DELETE} = METHODS; -function baseDriverE2ETests (DriverClass, defaultCaps = {}) { +function baseDriverE2ETests(DriverClass, defaultCaps = {}) { let address = defaultCaps['appium:address'] ?? TEST_HOST; let port = defaultCaps['appium:port']; const className = DriverClass.name || '(unknown driver)'; @@ -14,9 +14,9 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { describe(`BaseDriver E2E (as ${className})`, function () { let baseServer, d; /** - * This URL creates a new session - * @type {string} - **/ + * This URL creates a new session + * @type {string} + **/ let newSessionURL; /** @@ -33,13 +33,13 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { let createSessionURL; before(async function () { - port = port ?? await getTestPort(); + port = port ?? (await getTestPort()); defaultCaps = {...defaultCaps, 'appium:port': port}; d = new DriverClass({port, address}); baseServer = await server({ routeConfiguringFunction: routeConfiguringFunction(d), port, - hostname: TEST_HOST + hostname: TEST_HOST, }); createAppiumTestURL = createAppiumURL(address, port); newSessionURL = createAppiumTestURL('', 'session'); @@ -50,26 +50,32 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { await baseServer.close(); }); - async function startSession (caps) { - return (await axios({ - url: newSessionURL, - method: POST, - data: {capabilities: {alwaysMatch: caps, firstMatch: [{}]}}, - })).data.value; + async function startSession(caps) { + return ( + await axios({ + url: newSessionURL, + method: POST, + data: {capabilities: {alwaysMatch: caps, firstMatch: [{}]}}, + }) + ).data.value; } - async function endSession (id) { - return (await axios({ - url: createSessionURL(id), - method: DELETE, - validateStatus: null, - })).data.value; + async function endSession(id) { + return ( + await axios({ + url: createSessionURL(id), + method: DELETE, + validateStatus: null, + }) + ).data.value; } - async function getSession (id) { - return (await axios({ - url: createSessionURL(id), - })).data.value; + async function getSession(id) { + return ( + await axios({ + url: createSessionURL(id), + }) + ).data.value; } describe('session handling', function () { @@ -77,16 +83,20 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { const sessionIds = []; let times = 0; do { - const {sessionId} = (await axios({ - url: newSessionURL, - headers: { - 'X-Idempotency-Key': '123456', - }, - method: POST, - data: {capabilities: {alwaysMatch: defaultCaps, firstMatch: [{}]}}, - simple: false, - resolveWithFullResponse: true - })).data.value; + const {sessionId} = ( + await axios({ + url: newSessionURL, + headers: { + 'X-Idempotency-Key': '123456', + }, + method: POST, + data: { + capabilities: {alwaysMatch: defaultCaps, firstMatch: [{}]}, + }, + simple: false, + resolveWithFullResponse: true, + }) + ).data.value; sessionIds.push(sessionId); times++; @@ -105,14 +115,18 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { const reqs = []; let times = 0; do { - reqs.push(axios({ - url: newSessionURL, - headers: { - 'X-Idempotency-Key': '12345', - }, - method: POST, - data: {capabilities: {alwaysMatch: defaultCaps, firstMatch: [{}]}}, - })); + reqs.push( + axios({ + url: newSessionURL, + headers: { + 'X-Idempotency-Key': '12345', + }, + method: POST, + data: { + capabilities: {alwaysMatch: defaultCaps, firstMatch: [{}]}, + }, + }) + ); times++; } while (times < 2); const sessionIds = (await B.all(reqs)).map((x) => x.data.value.sessionId); @@ -149,13 +163,12 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { }); }); - it.skip('should throw NYI for commands not implemented', async function () { - }); + it.skip('should throw NYI for commands not implemented', async function () {}); describe('command timeouts', function () { let originalFindElement, originalFindElements; - async function startTimeoutSession (timeout) { + async function startTimeoutSession(timeout) { const caps = _.cloneDeep(defaultCaps); caps['appium:newCommandTimeout'] = timeout; return await startSession(caps); @@ -179,7 +192,6 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { d.findElements = originalFindElements; }); - it('should set a default commandTimeout', async function () { let newSession = await startTimeoutSession(); d.newCommandTimeoutMs.should.be.above(0); @@ -210,11 +222,13 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { it('should not timeout with commandTimeout of false', async function () { let newSession = await startTimeoutSession(0.1); let start = Date.now(); - const {value} = (await axios({ - url: createAppiumTestURL(d.sessionId, 'elements'), - method: POST, - data: {using: 'name', value: 'foo'}, - })).data; + const {value} = ( + await axios({ + url: createAppiumTestURL(d.sessionId, 'elements'), + method: POST, + data: {using: 'name', value: 'foo'}, + }) + ).data; (Date.now() - start).should.be.above(150); value.should.eql(['foo']); await endSession(newSession.sessionId); @@ -230,9 +244,11 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { data: {using: 'name', value: 'foo'}, }); await B.delay(400); - const {value} = (await axios({ - url: createSessionURL(d.sessionId), - })).data; + const {value} = ( + await axios({ + url: createSessionURL(d.sessionId), + }) + ).data; value.platformName.should.equal(defaultCaps.platformName); const resp = await endSession(newSession.sessionId); should.equal(resp, null); @@ -251,10 +267,12 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { data: {using: 'name', value: 'foo'}, }); await B.delay(400); - const {value} = (await axios({ - url: sessionURL, - validateStatus: null, - })).data; + const {value} = ( + await axios({ + url: sessionURL, + validateStatus: null, + }) + ).data; value.error.should.equal('invalid session id'); should.equal(d.sessionId, null); const resp = await endSession(newSession.sessionId); @@ -269,7 +287,6 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { await endSession(newSession.sessionId); should.not.exist(d.noCommandTimer); }); - }); describe('settings api', function () { @@ -283,8 +300,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { await d.settings.update({ignoreUnimportantViews: true}).should.not.be.rejected; }); it('should reject for invalid update object', async function () { - await d.settings.update('invalid json').should.eventually - .be.rejectedWith('JSON'); + await d.settings.update('invalid json').should.eventually.be.rejectedWith('JSON'); }); }); @@ -302,7 +318,15 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { // make sure that the request gets to the server before our shutdown await B.delay(100); const shutdownEventPromise = new B((resolve, reject) => { - setTimeout(() => reject(new Error('onUnexpectedShutdown event is expected to be fired within 5 seconds timeout')), 5000); + setTimeout( + () => + reject( + new Error( + 'onUnexpectedShutdown event is expected to be fired within 5 seconds timeout' + ) + ), + 5000 + ); d.onUnexpectedShutdown(resolve); }); d.startUnexpectedShutdown(new Error('Crashytimes')); @@ -323,9 +347,11 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { await endSession(session.sessionId); }); it('should add start session timings', async function () { - const caps = Object.assign({}, defaultCaps, {'appium:eventTimings': true}); + const caps = Object.assign({}, defaultCaps, { + 'appium:eventTimings': true, + }); const session = await startSession(caps); - const res = (await getSession(session.sessionId)); + const res = await getSession(session.sessionId); should.exist(res.events); should.exist(res.events.newSessionRequested); should.exist(res.events.newSessionStarted); @@ -346,7 +372,7 @@ function baseDriverE2ETests (DriverClass, defaultCaps = {}) { [PREFIXED_APPIUM_OPTS_CAP]: { platformVersion: '11.4', 'appium:deviceName': 'iPhone 11', - } + }, }); d.opts.platformVersion.should.eql('11.4'); d.opts.deviceName.should.eql('iPhone 11'); diff --git a/packages/base-driver/test/basedriver/driver-tests.js b/packages/base-driver/test/basedriver/driver-tests.js index 141e30937..dde00b469 100644 --- a/packages/base-driver/test/basedriver/driver-tests.js +++ b/packages/base-driver/test/basedriver/driver-tests.js @@ -1,12 +1,11 @@ import _ from 'lodash'; import B from 'bluebird'; -import { DeviceSettings } from '../../lib'; -import { createSandbox } from 'sinon'; - +import {DeviceSettings} from '../../lib'; +import {createSandbox} from 'sinon'; // wrap these tests in a function so we can export the tests and re-use them // for actual driver implementations -function baseDriverUnitTests (DriverClass, defaultCaps = {}) { +function baseDriverUnitTests(DriverClass, defaultCaps = {}) { // to display the driver under test in report const className = DriverClass.name || '(unknown driver)'; @@ -19,7 +18,6 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { }); describe('Log prefix', function () { - it('should setup log prefix', async function () { const d = new DriverClass(); const previousPrefix = d.log.prefix; @@ -37,7 +35,6 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { previousPrefix.should.eql(d.log.prefix); } }); - }); describe(`BaseDriver (as ${className})`, function () { @@ -79,7 +76,9 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { it('should not be able to start two sessions without closing the first', async function () { await d.createSession(null, null, _.cloneDeep(w3cCaps)); - await d.createSession(null, null, _.cloneDeep(w3cCaps)).should.eventually.be.rejectedWith('session'); + await d + .createSession(null, null, _.cloneDeep(w3cCaps)) + .should.eventually.be.rejectedWith('session'); }); it('should be able to delete a session', async function () { @@ -108,11 +107,11 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { sessions.length.should.equal(1); sessions[0].should.include({ - id: d.sessionId + id: d.sessionId, }); sessions[0].capabilities.should.include({ deviceName: 'Commodore 64', - platformName: 'Fake' + platformName: 'Fake', }); }); @@ -125,7 +124,15 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { let cmdPromise = d.executeCommand('getStatus'); await B.delay(10); const p = new B((resolve, reject) => { - setTimeout(() => reject(new Error('onUnexpectedShutdown event is expected to be fired within 5 seconds timeout')), 5000); + setTimeout( + () => + reject( + new Error( + 'onUnexpectedShutdown event is expected to be fired within 5 seconds timeout' + ) + ), + 5000 + ); d.onUnexpectedShutdown(resolve); }); d.startUnexpectedShutdown(new Error('We crashed')); @@ -142,7 +149,15 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { }.bind(d); await d.createSession(null, null, w3cCaps); const p = new B((resolve, reject) => { - setTimeout(() => reject(new Error('onUnexpectedShutdown event is expected to be fired within 5 seconds timeout')), 5000); + setTimeout( + () => + reject( + new Error( + 'onUnexpectedShutdown event is expected to be fired within 5 seconds timeout' + ) + ), + 5000 + ); d.onUnexpectedShutdown(resolve); }); d.startUnexpectedShutdown(new Error('We crashed')); @@ -160,7 +175,15 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { await d.createSession(null, null, _.cloneDeep(w3cCaps)); const p = new B((resolve, reject) => { - setTimeout(() => reject(new Error('onUnexpectedShutdown event is expected to be fired within 5 seconds timeout')), 5000); + setTimeout( + () => + reject( + new Error( + 'onUnexpectedShutdown event is expected to be fired within 5 seconds timeout' + ) + ), + 5000 + ); d.onUnexpectedShutdown(resolve); }); d.startUnexpectedShutdown(new Error('We crashed')); @@ -188,7 +211,10 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { describe('protocol detection', function () { it('should use W3C if only W3C caps are provided', async function () { - await d.createSession(null, null, {alwaysMatch: _.clone(defaultCaps), firstMatch: [{}]}); + await d.createSession(null, null, { + alwaysMatch: _.clone(defaultCaps), + firstMatch: [{}], + }); d.protocol.should.equal('W3C'); }); }); @@ -328,11 +354,12 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { platformName: 'Fake', 'appium:deviceName': 'Commodore 64', 'appium:fullReset': true, - 'appium:noReset': true + 'appium:noReset': true, }), }; - await d.createSession(null, null, newCaps).should.eventually.be.rejectedWith( - /noReset.+fullReset/); + await d + .createSession(null, null, newCaps) + .should.eventually.be.rejectedWith(/noReset.+fullReset/); }); }); @@ -349,7 +376,9 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { d.proxyActive(sessId).should.be.false; }); it('should throw an error when sessionId is wrong', function () { - (() => { d.proxyActive('aaa'); }).should.throw; + (() => { + d.proxyActive('aaa'); + }).should.throw; }); }); @@ -361,7 +390,9 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { d.getProxyAvoidList(sessId).should.be.an.instanceof(Array); }); it('should throw an error when sessionId is wrong', function () { - (() => { d.getProxyAvoidList('aaa'); }).should.throw; + (() => { + d.getProxyAvoidList('aaa'); + }).should.throw; }); }); @@ -373,7 +404,9 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { d.canProxy(sessId).should.be.a('boolean'); }); it('should throw an error when sessionId is wrong', function () { - (() => { d.canProxy(); }).should.throw; + (() => { + d.canProxy(); + }).should.throw; }); }); @@ -381,21 +414,38 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { it('should validate form of avoidance list', function () { const avoidStub = sandbox.stub(d, 'getProxyAvoidList'); avoidStub.returns([['POST', /\/foo/], ['GET']]); - (() => { d.proxyRouteIsAvoided(); }).should.throw; - avoidStub.returns([['POST', /\/foo/], ['GET', /^foo/, 'bar']]); - (() => { d.proxyRouteIsAvoided(); }).should.throw; + (() => { + d.proxyRouteIsAvoided(); + }).should.throw; + avoidStub.returns([ + ['POST', /\/foo/], + ['GET', /^foo/, 'bar'], + ]); + (() => { + d.proxyRouteIsAvoided(); + }).should.throw; avoidStub.restore(); }); it('should reject bad http methods', function () { const avoidStub = sandbox.stub(d, 'getProxyAvoidList'); - avoidStub.returns([['POST', /^foo/], ['BAZETE', /^bar/]]); - (() => { d.proxyRouteIsAvoided(); }).should.throw; + avoidStub.returns([ + ['POST', /^foo/], + ['BAZETE', /^bar/], + ]); + (() => { + d.proxyRouteIsAvoided(); + }).should.throw; avoidStub.restore(); }); it('should reject non-regex routes', function () { const avoidStub = sandbox.stub(d, 'getProxyAvoidList'); - avoidStub.returns([['POST', /^foo/], ['GET', '/bar']]); - (() => { d.proxyRouteIsAvoided(); }).should.throw; + avoidStub.returns([ + ['POST', /^foo/], + ['GET', '/bar'], + ]); + (() => { + d.proxyRouteIsAvoided(); + }).should.throw; avoidStub.restore(); }); it('should return true for routes in the avoid list', function () { @@ -425,7 +475,10 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { beforeEach(async function () { beforeStartTime = Date.now(); d.shouldValidateCaps = false; - await d.executeCommand('createSession', null, null, {alwaysMatch: {...defaultCaps}, firstMatch: [{}]}); + await d.executeCommand('createSession', null, null, { + alwaysMatch: {...defaultCaps}, + firstMatch: [{}], + }); }); describe('#eventHistory', function () { it('should have an eventHistory property', function () { @@ -492,12 +545,16 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) { describe('.reset', function () { it('should reset as W3C if the original session was W3C', async function () { const caps = { - alwaysMatch: Object.assign({}, { - 'appium:app': 'Fake', - 'appium:deviceName': 'Fake', - 'appium:automationName': 'Fake', - platformName: 'Fake', - }, defaultCaps), + alwaysMatch: Object.assign( + {}, + { + 'appium:app': 'Fake', + 'appium:deviceName': 'Fake', + 'appium:automationName': 'Fake', + platformName: 'Fake', + }, + defaultCaps + ), firstMatch: [{}], }; await d.createSession(undefined, undefined, caps); diff --git a/packages/base-driver/test/basedriver/index.js b/packages/base-driver/test/basedriver/index.js index 2b843a8e7..0257a35d4 100644 --- a/packages/base-driver/test/basedriver/index.js +++ b/packages/base-driver/test/basedriver/index.js @@ -3,4 +3,4 @@ import baseDriverUnitTests from './driver-tests'; import baseDriverE2ETests from './driver-e2e-tests'; -export { baseDriverUnitTests, baseDriverE2ETests }; +export {baseDriverUnitTests, baseDriverE2ETests}; diff --git a/packages/base-driver/test/e2e/basedriver/driver.e2e.spec.js b/packages/base-driver/test/e2e/basedriver/driver.e2e.spec.js index cc88f03f6..2f4b30d2c 100644 --- a/packages/base-driver/test/e2e/basedriver/driver.e2e.spec.js +++ b/packages/base-driver/test/e2e/basedriver/driver.e2e.spec.js @@ -4,5 +4,5 @@ import BaseDriver from '../../../lib'; import baseDriverE2ETests from '../../basedriver/driver-e2e-tests'; baseDriverE2ETests(BaseDriver, { platformName: 'iOS', - 'appium:deviceName': 'Delorean' + 'appium:deviceName': 'Delorean', }); diff --git a/packages/base-driver/test/e2e/basedriver/helpers.e2e.spec.js b/packages/base-driver/test/e2e/basedriver/helpers.e2e.spec.js index ce36c9d91..f19f76eb2 100644 --- a/packages/base-driver/test/e2e/basedriver/helpers.e2e.spec.js +++ b/packages/base-driver/test/e2e/basedriver/helpers.e2e.spec.js @@ -1,16 +1,15 @@ import path from 'path'; import url from 'url'; -import { fs } from '@appium/support'; -import { configureApp } from '../../../lib/basedriver/helpers'; +import {fs} from '@appium/support'; +import {configureApp} from '../../../lib/basedriver/helpers'; import http from 'http'; import finalhandler from 'finalhandler'; import serveStatic from 'serve-static'; import contentDisposition from 'content-disposition'; import B from 'bluebird'; -import { TEST_HOST, getTestPort } from '../../helpers'; +import {TEST_HOST, getTestPort} from '../../helpers'; - -function getFixture (file) { +function getFixture(file) { // XXX: __dirname disallowed in native ESM return path.resolve(__dirname, '..', 'fixtures', file); } @@ -42,16 +41,17 @@ describe('app download and configuration', function () { contents.should.eql('this is not really an app\n'); }); it('should fail for a bad zip file', async function () { - await configureApp(getFixture('BadZippedApp.zip'), '.app') - .should.be.rejectedWith(/PK/); + await configureApp(getFixture('BadZippedApp.zip'), '.app').should.be.rejectedWith(/PK/); }); it('should fail if extensions do not match', async function () { - await configureApp(getFixture('FakeIOSApp.app'), '.wrong') - .should.be.rejectedWith(/did not have extension/); + await configureApp(getFixture('FakeIOSApp.app'), '.wrong').should.be.rejectedWith( + /did not have extension/ + ); }); it('should fail if zip file does not contain an app whose extension matches', async function () { - await configureApp(getFixture('FakeIOSApp.app.zip'), '.wrong') - .should.be.rejectedWith(/did not have extension/); + await configureApp(getFixture('FakeIOSApp.app.zip'), '.wrong').should.be.rejectedWith( + /did not have extension/ + ); }); describe('should download an app from the web', function () { let port; @@ -64,8 +64,10 @@ describe('app download and configuration', function () { describe('server not available', function () { it('should handle server not available', async function () { - await configureApp(`${serverUrl}/FakeIOSApp.app.zip`, '.app') - .should.eventually.be.rejectedWith(/ECONNREFUSED/); + await configureApp( + `${serverUrl}/FakeIOSApp.app.zip`, + '.app' + ).should.eventually.be.rejectedWith(/ECONNREFUSED/); }); }); describe('server available', function () { @@ -117,7 +119,10 @@ describe('app download and configuration', function () { contents.should.eql('this is not really an app\n'); }); it('should download zip file with query string', async function () { - let newAppPath = await configureApp(`${serverUrl}/FakeIOSApp.app.zip?sv=abc&sr=def`, '.app'); + let newAppPath = await configureApp( + `${serverUrl}/FakeIOSApp.app.zip?sv=abc&sr=def`, + '.app' + ); newAppPath.should.contain('.app'); let contents = await fs.readFile(newAppPath, 'utf8'); contents.should.eql('this is not really an app\n'); @@ -141,42 +146,64 @@ describe('app download and configuration', function () { contents.should.eql('this is not really an apk\n'); }); it('should handle zip file that cannot be downloaded', async function () { - await configureApp(`${serverUrl}/missing/FakeIOSApp.app.zip`, '.app') - .should.eventually.be.rejected; + await configureApp(`${serverUrl}/missing/FakeIOSApp.app.zip`, '.app').should.eventually.be + .rejected; }); it('should handle invalid protocol', async function () { - await configureApp('file://C:/missing/FakeIOSApp.app.zip', '.app') - .should.eventually.be.rejectedWith(/is not supported/); - await configureApp(`ftp://${TEST_HOST}:${port}/missing/FakeIOSApp.app.zip`, '.app') - .should.eventually.be.rejectedWith(/is not supported/); + await configureApp( + 'file://C:/missing/FakeIOSApp.app.zip', + '.app' + ).should.eventually.be.rejectedWith(/is not supported/); + await configureApp( + `ftp://${TEST_HOST}:${port}/missing/FakeIOSApp.app.zip`, + '.app' + ).should.eventually.be.rejectedWith(/is not supported/); }); it('should handle missing file in Windows path format', async function () { - await configureApp('C:\\missing\\FakeIOSApp.app.zip', '.app') - .should.eventually.be.rejectedWith(/does not exist or is not accessible/); + await configureApp( + 'C:\\missing\\FakeIOSApp.app.zip', + '.app' + ).should.eventually.be.rejectedWith(/does not exist or is not accessible/); }); it('should recognize zip mime types and unzip the downloaded file', async function () { - let newAppPath = await configureApp(`${serverUrl}/FakeAndroidApp.asd?content-type=${encodeURIComponent('application/zip')}`, '.apk'); + let newAppPath = await configureApp( + `${serverUrl}/FakeAndroidApp.asd?content-type=${encodeURIComponent('application/zip')}`, + '.apk' + ); newAppPath.should.contain('FakeAndroidApp.apk'); newAppPath.should.not.contain('.asd'); let contents = await fs.readFile(newAppPath, 'utf8'); contents.should.eql('this is not really an apk\n'); }); it('should recognize zip mime types with parameter and unzip the downloaded file', async function () { - let newAppPath = await configureApp(`${serverUrl}/FakeAndroidApp.asd?content-type=${encodeURIComponent('application/zip; parameter=value')}`, '.apk'); + let newAppPath = await configureApp( + `${serverUrl}/FakeAndroidApp.asd?content-type=${encodeURIComponent( + 'application/zip; parameter=value' + )}`, + '.apk' + ); newAppPath.should.contain('FakeAndroidApp.apk'); newAppPath.should.not.contain('.asd'); let contents = await fs.readFile(newAppPath, 'utf8'); contents.should.eql('this is not really an apk\n'); }); it('should recognize zip mime types and unzip the downloaded file with query string', async function () { - let newAppPath = await configureApp(`${serverUrl}/FakeAndroidApp.asd?content-type=${encodeURIComponent('application/zip')}&sv=abc&sr=def`, '.apk'); + let newAppPath = await configureApp( + `${serverUrl}/FakeAndroidApp.asd?content-type=${encodeURIComponent( + 'application/zip' + )}&sv=abc&sr=def`, + '.apk' + ); newAppPath.should.contain('FakeAndroidApp.apk'); newAppPath.should.not.contain('.asd'); let contents = await fs.readFile(newAppPath, 'utf8'); contents.should.eql('this is not really an apk\n'); }); it('should treat an unknown mime type as an app', async function () { - let newAppPath = await configureApp(`${serverUrl}/FakeAndroidApp.apk?content-type=${encodeURIComponent('application/bip')}`, '.apk'); + let newAppPath = await configureApp( + `${serverUrl}/FakeAndroidApp.apk?content-type=${encodeURIComponent('application/bip')}`, + '.apk' + ); newAppPath.should.contain('.apk'); let contents = await fs.readFile(newAppPath, 'utf8'); contents.should.eql('this is not really an apk\n'); diff --git a/packages/base-driver/test/e2e/basedriver/websockets.e2e.spec.js b/packages/base-driver/test/e2e/basedriver/websockets.e2e.spec.js index e0c544b60..7e2592ce9 100644 --- a/packages/base-driver/test/e2e/basedriver/websockets.e2e.spec.js +++ b/packages/base-driver/test/e2e/basedriver/websockets.e2e.spec.js @@ -1,13 +1,10 @@ import _ from 'lodash'; -import { - server, routeConfiguringFunction, DEFAULT_WS_PATHNAME_PREFIX -} from '../../../lib'; -import { FakeDriver } from '../protocol/fake-driver'; +import {server, routeConfiguringFunction, DEFAULT_WS_PATHNAME_PREFIX} from '../../../lib'; +import {FakeDriver} from '../protocol/fake-driver'; import WebSocket from 'ws'; import B from 'bluebird'; import {TEST_HOST, getTestPort} from '../../helpers'; - describe('Websockets (e2e)', function () { let baseServer; let driver; @@ -60,8 +57,10 @@ describe('Websockets (e2e)', function () { resolve(); }); client.once('error', reject); - setTimeout(() => reject(new Error('No websocket messages have been received after the timeout')), - timeout); + setTimeout( + () => reject(new Error('No websocket messages have been received after the timeout')), + timeout + ); }); (await baseServer.removeWebSocketHandler(endpoint)).should.be.true; @@ -69,8 +68,12 @@ describe('Websockets (e2e)', function () { await new B((resolve, reject) => { const client = new WebSocket(`ws://${TEST_HOST}:${port}${endpoint}`); client.on('message', (data) => - reject(new Error(`No websocket messages are expected after the handler ` + - `has been removed. '${data}' is received instead. `)) + reject( + new Error( + `No websocket messages are expected after the handler ` + + `has been removed. '${data}' is received instead. ` + ) + ) ); client.on('error', resolve); setTimeout(resolve, timeout); diff --git a/packages/base-driver/test/e2e/express/server.e2e.spec.js b/packages/base-driver/test/e2e/express/server.e2e.spec.js index 5d0a3e6bc..e25bd0ac5 100644 --- a/packages/base-driver/test/e2e/express/server.e2e.spec.js +++ b/packages/base-driver/test/e2e/express/server.e2e.spec.js @@ -1,13 +1,12 @@ // transpile:mocha -import { server } from '../../../lib'; +import {server} from '../../../lib'; import axios from 'axios'; -import { createSandbox } from 'sinon'; +import {createSandbox} from 'sinon'; import B from 'bluebird'; import _ from 'lodash'; import {TEST_HOST, getTestPort} from '../../helpers'; - describe('server', function () { let hwServer; let port; @@ -15,7 +14,7 @@ describe('server', function () { before(async function () { port = await getTestPort(true); - function configureRoutes (app) { + function configureRoutes(app) { app.get('/', (req, res) => { res.header['content-type'] = 'text/html'; res.status(200).send('Hello World!'); @@ -57,8 +56,8 @@ describe('server', function () { url: `http://${TEST_HOST}:${port}/python`, headers: { 'user-agent': 'Python', - 'content-type': 'application/x-www-form-urlencoded' - } + 'content-type': 'application/x-www-form-urlencoded', + }, }); data.should.eql('application/json; charset=utf-8'); }); @@ -67,7 +66,7 @@ describe('server', function () { }); it('should error if we try to start again on a port that is used', async function () { await server({ - routeConfiguringFunction () {}, + routeConfiguringFunction() {}, port, }).should.be.rejectedWith(/EADDRINUSE/); }); @@ -113,8 +112,9 @@ describe('server plugins', function () { } catch (ign) {} }); - function updaterWithGetRoute (route, reply) { - return async (app, httpServer) => { // eslint-disable-line require-await + function updaterWithGetRoute(route, reply) { + // eslint-disable-next-line require-await + return async (app, httpServer) => { app.get(`/${route}`, (req, res) => { res.header['content-type'] = 'text/html'; res.status(200).send(reply); @@ -130,7 +130,7 @@ describe('server plugins', function () { serverUpdaters: [ updaterWithGetRoute('plugin1', 'res from plugin1 route'), updaterWithGetRoute('plugin2', 'res from plugin2 route'), - ] + ], }); let {data} = await axios.get(`http://${TEST_HOST}:${port}/plugin1`); data.should.eql('res from plugin1 route'); @@ -143,7 +143,11 @@ describe('server plugins', function () { await server({ routeConfiguringFunction: _.noop, port, - serverUpdaters: [() => { throw new Error('ugh');}] + serverUpdaters: [ + () => { + throw new Error('ugh'); + }, + ], }).should.eventually.be.rejectedWith(/ugh/); }); }); diff --git a/packages/base-driver/test/e2e/jsonwp-proxy/proxy.e2e.spec.js b/packages/base-driver/test/e2e/jsonwp-proxy/proxy.e2e.spec.js index b9078267a..95a9f2627 100644 --- a/packages/base-driver/test/e2e/jsonwp-proxy/proxy.e2e.spec.js +++ b/packages/base-driver/test/e2e/jsonwp-proxy/proxy.e2e.spec.js @@ -1,5 +1,5 @@ -import { JWProxy, server, routeConfiguringFunction } from '../../../lib'; -import { FakeDriver } from '../protocol/fake-driver'; +import {JWProxy, server, routeConfiguringFunction} from '../../../lib'; +import {FakeDriver} from '../protocol/fake-driver'; describe('proxy', function () { const jwproxy = new JWProxy(); @@ -29,7 +29,9 @@ describe('proxy', function () { }); it('should start a new session', async function () { const caps = {browserName: 'fake'}; - const res = await jwproxy.command('/session', 'POST', {capabilities: {alwaysMatch: caps}}); + const res = await jwproxy.command('/session', 'POST', { + capabilities: {alwaysMatch: caps}, + }); res.capabilities.alwaysMatch.should.have.property('browserName'); jwproxy.sessionId.should.have.length(48); }); diff --git a/packages/base-driver/test/e2e/protocol/fake-driver.js b/packages/base-driver/test/e2e/protocol/fake-driver.js index f4c9db34a..2e823d34e 100644 --- a/packages/base-driver/test/e2e/protocol/fake-driver.js +++ b/packages/base-driver/test/e2e/protocol/fake-driver.js @@ -1,42 +1,40 @@ /* eslint-disable require-await */ -import { errors, BaseDriver, determineProtocol } from '../../../lib'; -import { PROTOCOLS } from '../../../lib/constants'; -import { util } from '@appium/support'; - +import {errors, BaseDriver, determineProtocol} from '../../../lib'; +import {PROTOCOLS} from '../../../lib/constants'; +import {util} from '@appium/support'; class FakeDriver extends BaseDriver { - static newMethodMap = { '/session/:sessionId/noproxy': { - GET: {command: 'notProxiedCommand', neverProxy: true} - } + GET: {command: 'notProxiedCommand', neverProxy: true}, + }, }; - constructor () { + constructor() { super(); this.protocol = PROTOCOLS.MJSONWP; this.sessionId = null; this.jwpProxyActive = false; } - sessionExists (sessionId) { + sessionExists(sessionId) { if (!sessionId) { return false; } return sessionId === this.sessionId; } - driverForSession (/*sessionId*/) { + driverForSession(/*sessionId*/) { return this; } - async createSession (desiredCapabilities, requiredCapabilities, capabilities) { + async createSession(desiredCapabilities, requiredCapabilities, capabilities) { // Use a counter to make sure each session has a unique id this.sessionId = `fakeSession_${util.uuidV4()}`; return [this.sessionId, capabilities]; } - async executeCommand (cmd, ...args) { + async executeCommand(cmd, ...args) { if (!this[cmd]) { throw new errors.NotYetImplementedError(); } @@ -46,101 +44,101 @@ class FakeDriver extends BaseDriver { return await this[cmd](...args); } - async deleteSession () { + async deleteSession() { this.jwpProxyActive = false; this.sessionId = null; } - async getStatus () { + async getStatus() { return "I'm fine"; } - async setUrl (url) { + async setUrl(url) { return `Navigated to: ${url}`; } - async getUrl () { + async getUrl() { return 'http://foobar.com'; } - async back (sessionId) { + async back(sessionId) { return sessionId; } - async forward () {} + async forward() {} - async refresh () { + async refresh() { throw new Error('Too Fresh!'); } - async getSession () { + async getSession() { throw new errors.NoSuchDriverError(); } - async click (elementId, sessionId) { + async click(elementId, sessionId) { return [elementId, sessionId]; } - async implicitWait (ms) { + async implicitWait(ms) { return ms; } - async clickCurrent (button) { + async clickCurrent(button) { return button; } - async setNetworkConnection (type) { + async setNetworkConnection(type) { return type; } - async moveTo (element, xOffset, yOffset) { + async moveTo(element, xOffset, yOffset) { return [element, xOffset, yOffset]; } - async getText () { + async getText() { return ''; } - async getAttribute (attr, elementId, sessionId) { + async getAttribute(attr, elementId, sessionId) { return [attr, elementId, sessionId]; } - async setValue (value, elementId) { + async setValue(value, elementId) { return [value, elementId]; } - async performTouch (...args) { + async performTouch(...args) { return args; } - async setFrame (frameId) { + async setFrame(frameId) { return frameId; } - async removeApp (app) { + async removeApp(app) { return app; } - async receiveAsyncResponse () { + async receiveAsyncResponse() { // this is here to test a failing command that does not throw an error return {status: 13, value: 'Mishandled Driver Error'}; } - proxyActive (/*sessionId*/) { + proxyActive(/*sessionId*/) { return false; } - getProxyAvoidList (/*sessionId*/) { + getProxyAvoidList(/*sessionId*/) { return []; } - canProxy (/*sessionId*/) { + canProxy(/*sessionId*/) { return false; } - async notProxiedCommand () { + async notProxiedCommand() { return 'This was not proxied'; } } -export { FakeDriver }; +export {FakeDriver}; diff --git a/packages/base-driver/test/e2e/protocol/helpers.js b/packages/base-driver/test/e2e/protocol/helpers.js index efe8154c5..f68c212d4 100644 --- a/packages/base-driver/test/e2e/protocol/helpers.js +++ b/packages/base-driver/test/e2e/protocol/helpers.js @@ -1,7 +1,7 @@ import Express from 'express'; import bodyParser from 'body-parser'; -export function createProxyServer (sessionId, port) { +export function createProxyServer(sessionId, port) { // Start an express server for proxying let app = new Express(); app.use(bodyParser.json()); diff --git a/packages/base-driver/test/e2e/protocol/protocol.e2e.spec.js b/packages/base-driver/test/e2e/protocol/protocol.e2e.spec.js index c5bc6ba2f..09e7b53c6 100644 --- a/packages/base-driver/test/e2e/protocol/protocol.e2e.spec.js +++ b/packages/base-driver/test/e2e/protocol/protocol.e2e.spec.js @@ -1,16 +1,12 @@ // transpile:mocha -import { - server, routeConfiguringFunction, errors, JWProxy, BaseDriver -} from '../../../lib'; -import { FakeDriver } from './fake-driver'; +import {server, routeConfiguringFunction, errors, JWProxy, BaseDriver} from '../../../lib'; +import {FakeDriver} from './fake-driver'; import axios from 'axios'; -import { createSandbox } from 'sinon'; -import { StatusCodes as HTTPStatusCodes } from 'http-status-codes'; -import { createProxyServer } from './helpers'; -import { - MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY -} from '../../../lib/constants'; +import {createSandbox} from 'sinon'; +import {StatusCodes as HTTPStatusCodes} from 'http-status-codes'; +import {createProxyServer} from './helpers'; +import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY} from '../../../lib/constants'; import {TEST_HOST, getTestPort} from '../../helpers'; let port; @@ -63,11 +59,11 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session/foo/url`, method: 'POST', - data: {url: 'http://google.com'} + data: {url: 'http://google.com'}, }); data.should.eql({ value: 'Navigated to: http://google.com', - sessionId: 'foo' + sessionId: 'foo', }); }); @@ -79,7 +75,7 @@ describe('Protocol', function () { }); data.should.eql({ value: 'Navigated to: http://google.com', - sessionId: 'foo' + sessionId: 'foo', }); }); @@ -96,7 +92,7 @@ describe('Protocol', function () { }); data.should.eql({ value: 'Navigated to: http://google.com', - sessionId: 'foo' + sessionId: 'foo', }); }); @@ -108,7 +104,7 @@ describe('Protocol', function () { }); data.should.eql({ value: 'foo', - sessionId: 'foo' + sessionId: 'foo', }); }); @@ -143,19 +139,18 @@ describe('Protocol', function () { await axios({ url: `${baseUrl}/session/foo/url`, method: 'POST', - data: 'oh hello' + data: 'oh hello', }).should.eventually.be.rejected; const {data} = await axios({ url: `${baseUrl}/session/foo/url`, method: 'POST', - data: {url: 'http://google.com'} + data: {url: 'http://google.com'}, }); data.should.eql({ value: 'Navigated to: http://google.com', - sessionId: 'foo' + sessionId: 'foo', }); - }); it('should get 404 for bad routes', async function () { @@ -203,7 +198,7 @@ describe('Protocol', function () { await axios({ url: `${baseUrl}/session/foo/url`, method: 'POST', - data: {} + data: {}, }).should.eventually.be.rejectedWith(/400/); }); @@ -211,13 +206,13 @@ describe('Protocol', function () { await axios({ url: `${baseUrl}/session/foo/element/bar/value`, method: 'POST', - data: {id: 'baz', sessionId: 'lol', value: ['a']} + data: {id: 'baz', sessionId: 'lol', value: ['a']}, }); await axios({ url: `${baseUrl}/session/foo/element/bar/value`, method: 'POST', - data: {id: 'baz'} + data: {id: 'baz'}, }).should.eventually.be.rejectedWith(/400/); // make sure adding the optional 'id' doesn't clobber a route where we @@ -225,7 +220,7 @@ describe('Protocol', function () { await axios({ url: `${baseUrl}/session/foo/frame`, method: 'POST', - data: {id: 'baz'} + data: {id: 'baz'}, }); }); @@ -238,8 +233,10 @@ describe('Protocol', function () { }); status.should.equal(500); data.value.error.should.eql('unknown error'); - data.value.message.should.eql('An unknown server-side error occurred while processing ' + - 'the command. Original error: Mishandled Driver Error'); + data.value.message.should.eql( + 'An unknown server-side error occurred while processing ' + + 'the command. Original error: Mishandled Driver Error' + ); data.sessionId.should.eql('foo'); }); @@ -248,7 +245,7 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session/foo/element/bar/value`, method: 'POST', - data: {value: 'text to type'} + data: {value: 'text to type'}, }); data.value.should.eql(['text to type', 'bar']); }); @@ -256,7 +253,7 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session/foo/element/bar/value`, method: 'POST', - data: {text: 'text to type'} + data: {text: 'text to type'}, }); data.value.should.eql(['text to type', 'bar']); }); @@ -264,7 +261,7 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session/foo/element/bar/value`, method: 'POST', - data: {value: 'text to type', text: 'text to ignore'} + data: {value: 'text to type', text: 'text to ignore'}, }); data.value.should.eql(['text to type', 'bar']); }); @@ -276,7 +273,7 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session/foo/moveto`, method: 'POST', - data: {element: '3'} + data: {element: '3'}, }); data.value.should.eql(['3', null, null]); }); @@ -284,7 +281,7 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session/foo/moveto`, method: 'POST', - data: {xoffset: 42, yoffset: 17} + data: {xoffset: 42, yoffset: 17}, }); data.value.should.eql([null, 42, 17]); }); @@ -294,7 +291,7 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session/foo/appium/device/remove_app`, method: 'POST', - data: {appId: 42} + data: {appId: 42}, }); data.value.should.eql(42); }); @@ -302,7 +299,7 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session/foo/appium/device/remove_app`, method: 'POST', - data: {bundleId: 42} + data: {bundleId: 42}, }); data.value.should.eql(42); }); @@ -314,20 +311,19 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session/foo/touch/perform`, method: 'POST', - data: [{'action': 'tap', 'options': {'element': '3'}}] + data: [{action: 'tap', options: {element: '3'}}], }); - data.value.should.deep.equal([[{'action': 'tap', 'options': {'element': '3'}}], 'foo']); + data.value.should.deep.equal([[{action: 'tap', options: {element: '3'}}], 'foo']); }); it('should not wrap twice', async function () { const {data} = await axios({ url: `${baseUrl}/session/foo/touch/perform`, method: 'POST', - data: {actions: [{'action': 'tap', 'options': {'element': '3'}}]} + data: {actions: [{action: 'tap', options: {element: '3'}}]}, }); - data.value.should.deep.equal([[{'action': 'tap', 'options': {'element': '3'}}], 'foo']); + data.value.should.deep.equal([[{action: 'tap', options: {element: '3'}}], 'foo']); }); - }); describe('create sessions via HTTP endpoint', function () { @@ -347,7 +343,7 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session`, method: 'POST', - data: {desiredCapabilities} + data: {desiredCapabilities}, }); should.equal(data.value, null); }); @@ -363,7 +359,7 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session`, method: 'POST', - data: {capabilities: w3cCapabilities} + data: {capabilities: w3cCapabilities}, }); should.not.exist(data.status); should.not.exist(data.sessionId); @@ -372,10 +368,12 @@ describe('Protocol', function () { sessionId = data.value.sessionId; }); it('should raise an error if the driver does not support W3C yet', async function () { - const createSessionStub = sandbox.stub(driver, 'createSession').callsFake(function (capabilities) { - driver.sessionId = null; - return BaseDriver.prototype.createSession.call(driver, capabilities); - }); + const createSessionStub = sandbox + .stub(driver, 'createSession') + .callsFake(function (capabilities) { + driver.sessionId = null; + return BaseDriver.prototype.createSession.call(driver, capabilities); + }); try { // let {status, value, sessionId} = await request({ await axios({ @@ -389,7 +387,7 @@ describe('Protocol', function () { }, firstMatch: [{}], }, - } + }, }).should.eventually.be.rejectedWith(/500/); } finally { createSessionStub.restore(); @@ -401,19 +399,21 @@ describe('Protocol', function () { beforeEach(async function () { // Start a W3C session - const {value} = (await axios({ - url: `${baseUrl}/session`, - method: 'POST', - data: { - capabilities: { - alwaysMatch: { - platformName: 'Fake', - 'appium:deviceName': 'Commodore 64', + const {value} = ( + await axios({ + url: `${baseUrl}/session`, + method: 'POST', + data: { + capabilities: { + alwaysMatch: { + platformName: 'Fake', + 'appium:deviceName': 'Commodore 64', + }, + firstMatch: [{}], }, - firstMatch: [{}], }, - }, - })).data; + }) + ).data; sessionId = value.sessionId; sessionUrl = `${baseUrl}/session/${sessionId}`; }); @@ -425,7 +425,7 @@ describe('Protocol', function () { validateStatus: null, data: { bad: 'params', - } + }, }); status.should.equal(400); @@ -456,14 +456,16 @@ describe('Protocol', function () { }); it(`should throw 500 Unknown Error if the command throws an unexpected exception`, async function () { - driver.performActions = () => { throw new Error(`Didn't work`); }; + driver.performActions = () => { + throw new Error(`Didn't work`); + }; const {status, data} = await axios({ url: `${sessionUrl}/actions`, method: 'POST', validateStatus: null, data: { actions: [], - } + }, }); status.should.equal(500); @@ -481,10 +483,11 @@ describe('Protocol', function () { { something: { [MJSONWP_ELEMENT_KEY]: 'fooo', - other: 'bar' - } - }, { - [MJSONWP_ELEMENT_KEY]: 'bar' + other: 'bar', + }, + }, + { + [MJSONWP_ELEMENT_KEY]: 'bar', }, 'ignore', ]; @@ -494,11 +497,12 @@ describe('Protocol', function () { something: { [MJSONWP_ELEMENT_KEY]: 'fooo', [W3C_ELEMENT_KEY]: 'fooo', - other: 'bar' - } - }, { + other: 'bar', + }, + }, + { [MJSONWP_ELEMENT_KEY]: 'bar', - [W3C_ELEMENT_KEY]: 'bar' + [W3C_ELEMENT_KEY]: 'bar', }, 'ignore', ]; @@ -515,7 +519,7 @@ describe('Protocol', function () { it(`should fail with a 408 error if it throws a TimeoutError exception`, async function () { let setUrlStub = sandbox.stub(driver, 'setUrl').callsFake(function () { - throw new errors.TimeoutError; + throw new errors.TimeoutError(); }); const {status, data} = await axios({ url: `${sessionUrl}/url`, @@ -523,7 +527,7 @@ describe('Protocol', function () { validateStatus: null, data: { url: 'https://example.com/', - } + }, }); status.should.equal(408); @@ -538,9 +542,11 @@ describe('Protocol', function () { it(`should pass with 200 HTTP status code if the command returns a value`, async function () { driver.performActions = (actions) => 'It works ' + actions.join(''); - const {status, value, sessionId} = (await axios.post(`${sessionUrl}/actions`, { - actions: ['a', 'b', 'c'], - })).data; + const {status, value, sessionId} = ( + await axios.post(`${sessionUrl}/actions`, { + actions: ['a', 'b', 'c'], + }) + ).data; should.not.exist(sessionId); should.not.exist(status); value.should.equal('It works abc'); @@ -561,7 +567,8 @@ describe('Protocol', function () { app = res.app; jwproxy = new JWProxy({host: TEST_HOST, port}); jwproxy.sessionId = sessionId; - driver.performActions = async (actions) => await jwproxy.command('/perform-actions', 'POST', actions); + driver.performActions = async (actions) => + await jwproxy.command('/perform-actions', 'POST', actions); }); afterEach(async function () { @@ -578,9 +585,11 @@ describe('Protocol', function () { }); }); - const {status, value, sessionId} = (await axios.post(`${sessionUrl}/actions`, { - actions: [1, 2, 3], - })).data; + const {status, value, sessionId} = ( + await axios.post(`${sessionUrl}/actions`, { + actions: [1, 2, 3], + }) + ).data; value.should.eql([1, 2, 3]); should.not.exist(status); should.not.exist(sessionId); @@ -600,7 +609,7 @@ describe('Protocol', function () { validateStatus: null, data: { actions: [1, 2, 3], - } + }, }); status.should.equal(HTTPStatusCodes.NOT_FOUND); JSON.stringify(data).should.match(/A problem occurred/); @@ -641,7 +650,7 @@ describe('Protocol', function () { validateStatus: null, data: { actions: [1, 2, 3], - } + }, }); status.should.equal(HTTPStatusCodes.NOT_FOUND); const {error: w3cError, message: errMessage, stacktrace} = data.value; @@ -666,7 +675,7 @@ describe('Protocol', function () { validateStatus: null, data: { actions: [1, 2, 3], - } + }, }); status.should.equal(HTTPStatusCodes.NOT_FOUND); const {error: w3cError, stacktrace} = data.value; @@ -691,14 +700,13 @@ describe('Protocol', function () { validateStatus: null, data: { actions: [1, 2, 3], - } + }, }); status.should.equal(HTTPStatusCodes.INTERNAL_SERVER_ERROR); const {error: w3cError, stacktrace} = data.value; w3cError.should.equal('unknown error'); stacktrace.should.match(/arbitrary stacktrace/); }); - }); }); }); @@ -710,7 +718,7 @@ describe('Protocol', function () { }); data.should.eql({ value: null, - sessionId: 'foo' + sessionId: 'foo', }); }); @@ -720,7 +728,7 @@ describe('Protocol', function () { }); data.should.eql({ value: '', - sessionId: 'foo' + sessionId: 'foo', }); }); @@ -733,8 +741,10 @@ describe('Protocol', function () { status.should.equal(500); data.value.error.should.eql('unknown error'); - data.value.message.should.eql('An unknown server-side error occurred while processing ' + - 'the command. Original error: Too Fresh!'); + data.value.message.should.eql( + 'An unknown server-side error occurred while processing ' + + 'the command. Original error: Too Fresh!' + ); data.sessionId.should.eql('foo'); }); @@ -785,7 +795,7 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session/${sessionId}/url`, method: 'POST', - data: {url: 'http://google.com'} + data: {url: 'http://google.com'}, }); should.exist(data.sessionId); @@ -833,8 +843,10 @@ describe('Protocol', function () { status.should.equal(500); data.value.error.should.eql('unknown error'); - data.value.message.should.eql('An unknown server-side error occurred while processing ' + - 'the command. Original error: Too Fresh!'); + data.value.message.should.eql( + 'An unknown server-side error occurred while processing ' + + 'the command. Original error: Too Fresh!' + ); data.sessionId.should.eql('Vader Sessions'); }); @@ -842,12 +854,20 @@ describe('Protocol', function () { const {data} = await axios({ url: `${baseUrl}/session`, method: 'POST', - data: {capabilities: {alwaysMatch: {'appium:greeting': 'hello'}, firstMatch: [{}]}} + data: { + capabilities: { + alwaysMatch: {'appium:greeting': 'hello'}, + firstMatch: [{}], + }, + }, }); should.exist(data.value.sessionId); data.value.sessionId.indexOf('fakeSession_').should.equal(0); - data.value.capabilities.should.eql({alwaysMatch: {'appium:greeting': 'hello'}, firstMatch: [{}]}); + data.value.capabilities.should.eql({ + alwaysMatch: {'appium:greeting': 'hello'}, + firstMatch: [{}], + }); }); }); @@ -865,7 +885,7 @@ describe('Protocol', function () { mjsonwpServer = await server({ routeConfiguringFunction: routeConfiguringFunction(driver), port, - extraMethodMap: FakeDriver.newMethodMap + extraMethodMap: FakeDriver.newMethodMap, }); }); @@ -884,14 +904,17 @@ describe('Protocol', function () { status.should.equal(500); data.value.error.should.eql('unknown error'); - data.value.message.should.eql('An unknown server-side error occurred while processing ' + - 'the command. Original error: Trying to proxy to a ' + - 'server but the driver is unable to proxy'); + data.value.message.should.eql( + 'An unknown server-side error occurred while processing ' + + 'the command. Original error: Trying to proxy to a ' + + 'server but the driver is unable to proxy' + ); data.sessionId.should.eql('foo'); }); it('should pass on any errors in proxying', async function () { - driver.proxyReqRes = async function () { // eslint-disable-line require-await + // eslint-disable-next-line require-await + driver.proxyReqRes = async function () { throw new Error('foo'); }; const {status, data} = await axios({ @@ -903,14 +926,21 @@ describe('Protocol', function () { status.should.equal(500); data.value.error.should.eql('unknown error'); - data.value.message.should.eql('An unknown server-side error occurred while processing ' + - 'the command. Original error: Could not proxy. Proxy error: foo'); + data.value.message.should.eql( + 'An unknown server-side error occurred while processing ' + + 'the command. Original error: Could not proxy. Proxy error: foo' + ); data.sessionId.should.eql('foo'); }); it('should able to throw ProxyRequestError in proxying', async function () { - driver.proxyReqRes = async function () { // eslint-disable-line require-await - let jsonwp = {status: 35, value: 'No such context found.', sessionId: 'foo'}; + // eslint-disable-next-line require-await + driver.proxyReqRes = async function () { + let jsonwp = { + status: 35, + value: 'No such context found.', + sessionId: 'foo', + }; throw new errors.ProxyRequestError(`Could not proxy command to remote server. `, jsonwp); }; const {status, data} = await axios({ @@ -927,13 +957,14 @@ describe('Protocol', function () { }); it('should let the proxy handle req/res', async function () { - driver.proxyReqRes = async function (req, res) { // eslint-disable-line require-await + // eslint-disable-next-line require-await + driver.proxyReqRes = async function (req, res) { res.status(200).json({custom: 'data'}); }; const {status, data} = await axios({ url: `${baseUrl}/session/${sessionId}/url`, method: 'POST', - data: {url: 'http://google.com'} + data: {url: 'http://google.com'}, }); status.should.equal(200); @@ -951,12 +982,12 @@ describe('Protocol', function () { status.should.equal(200); data.should.eql({ value: 'Navigated to: http://google.com', - sessionId + sessionId, }); }); it('should fail if avoid proxy list is malformed in some way', async function () { - async function badProxyAvoidanceList (list) { + async function badProxyAvoidanceList(list) { driver.getProxyAvoidList = () => list; const {status, data} = await axios({ url: `${baseUrl}/session/${sessionId}/url`, @@ -968,12 +999,7 @@ describe('Protocol', function () { status.should.equal(500); data.value.message.should.contain('roxy'); } - const lists = [ - 'foo', - [['foo']], - [['BAR', /lol/]], - [['GET', 'foo']] - ]; + const lists = ['foo', [['foo']], [['BAR', /lol/]], [['GET', 'foo']]]; for (let list of lists) { await badProxyAvoidanceList(list); } @@ -989,7 +1015,7 @@ describe('Protocol', function () { status.should.equal(200); data.should.eql({ value: "I'm fine", - sessionId: null + sessionId: null, }); }); @@ -1013,9 +1039,8 @@ describe('Protocol', function () { status.should.equal(200); data.should.eql({ value: 'This was not proxied', - sessionId + sessionId, }); }); - }); }); diff --git a/packages/base-driver/test/helpers.js b/packages/base-driver/test/helpers.js index 0e9268761..43282dc3b 100644 --- a/packages/base-driver/test/helpers.js +++ b/packages/base-driver/test/helpers.js @@ -8,7 +8,7 @@ const TEST_HOST = '127.0.0.1'; */ const METHODS = { POST: 'POST', - DELETE: 'DELETE' + DELETE: 'DELETE', }; let testPort; @@ -17,7 +17,7 @@ let testPort; * @param {boolean} [force] - If true, do not reuse the port (if it already exists) * @returns {Promise} a free port */ -async function getTestPort (force = false) { +async function getTestPort(force = false) { if (force || !testPort) { let port = await getPort(); if (!testPort) { @@ -66,7 +66,7 @@ const createAppiumURL = curry( let url = new URL(path, `${address}:${port}`); return url.href; }, - 4, + 4 ); export {TEST_HOST, METHODS, getTestPort, createAppiumURL}; diff --git a/packages/base-driver/test/unit/basedriver/capabilities.spec.js b/packages/base-driver/test/unit/basedriver/capabilities.spec.js index 7b789eead..48ac0bdf7 100644 --- a/packages/base-driver/test/unit/basedriver/capabilities.spec.js +++ b/packages/base-driver/test/unit/basedriver/capabilities.spec.js @@ -1,19 +1,25 @@ import { - parseCaps, validateCaps, mergeCaps, processCapabilities, findNonPrefixedCaps, - promoteAppiumOptions, APPIUM_OPTS_CAP, stripAppiumPrefixes + parseCaps, + validateCaps, + mergeCaps, + processCapabilities, + findNonPrefixedCaps, + promoteAppiumOptions, + APPIUM_OPTS_CAP, + stripAppiumPrefixes, } from '../../../lib/basedriver/capabilities'; import _ from 'lodash'; -import { desiredCapabilityConstraints } from '../../../lib/basedriver/desired-caps'; -import { isW3cCaps } from '../../../lib/helpers/capabilities'; - +import {desiredCapabilityConstraints} from '../../../lib/basedriver/desired-caps'; +import {isW3cCaps} from '../../../lib/helpers/capabilities'; describe('caps', function () { - // Tests based on: https://www.w3.org/TR/webdriver/#dfn-validate-caps describe('#validateCaps', function () { it('returns invalid argument error if "capability" is not a JSON object (1)', function () { for (let arg of [undefined, null, 1, true, 'string']) { - (function () { validateCaps(arg); }).should.throw(/must be a JSON object/); + (function () { + validateCaps(arg); + }.should.throw(/must be a JSON object/)); } }); @@ -27,27 +33,40 @@ describe('caps', function () { }); it('returns the capability that was passed in if "skipPresenceConstraint" is false', function () { - validateCaps({}, {foo: {presence: true}}, {skipPresenceConstraint: true}).should.deep.equal({}); + validateCaps({}, {foo: {presence: true}}, {skipPresenceConstraint: true}).should.deep.equal( + {} + ); }); it('returns invalid argument error if "isString" constraint not met on property', function () { - (() => validateCaps({foo: 1}, {foo: {isString: true}})).should.throw(/'foo' must be of type string/); + (() => validateCaps({foo: 1}, {foo: {isString: true}})).should.throw( + /'foo' must be of type string/ + ); }); it('returns invalid argument error if "isNumber" constraint not met on property', function () { - (() => validateCaps({foo: 'bar'}, {foo: {isNumber: true}})).should.throw(/'foo' must be of type number/); + (() => validateCaps({foo: 'bar'}, {foo: {isNumber: true}})).should.throw( + /'foo' must be of type number/ + ); }); it('returns invalid argument error if "isBoolean" constraint not met on property', function () { - (() => validateCaps({foo: 'bar'}, {foo: {isBoolean: true}})).should.throw(/'foo' must be of type boolean/); + (() => validateCaps({foo: 'bar'}, {foo: {isBoolean: true}})).should.throw( + /'foo' must be of type boolean/ + ); }); it('returns invalid argument error if "inclusion" constraint not met on property', function () { - (() => validateCaps({foo: '3'}, {foo: {inclusionCaseInsensitive: ['1', '2']}})).should.throw(/'foo' 3 not part of 1,2/); + (() => + validateCaps({foo: '3'}, {foo: {inclusionCaseInsensitive: ['1', '2']}})).should.throw( + /'foo' 3 not part of 1,2/ + ); }); it('returns invalid argument error if "inclusionCaseInsensitive" constraint not met on property', function () { - (() => validateCaps({foo: 'a'}, {foo: {inclusion: ['A', 'B', 'C']}})).should.throw(/'foo' a is not included in the list/); + (() => validateCaps({foo: 'a'}, {foo: {inclusion: ['A', 'B', 'C']}})).should.throw( + /'foo' a is not included in the list/ + ); }); }); @@ -81,7 +100,9 @@ describe('caps', function () { }); it('returns invalid argument error if primary and secondary have matching properties (4)', function () { - (() => mergeCaps({hello: 'world'}, {hello: 'whirl'})).should.throw(/property 'hello' should not exist on both primary [\w\W]* and secondary [\w\W]*/); + (() => mergeCaps({hello: 'world'}, {hello: 'whirl'})).should.throw( + /property 'hello' should not exist on both primary [\w\W]* and secondary [\w\W]*/ + ); }); it('returns a result with keys from primary and secondary together', function () { @@ -94,7 +115,10 @@ describe('caps', function () { d: 'd', }; mergeCaps(primary, secondary).should.deep.equal({ - a: 'a', b: 'b', c: 'c', d: 'd', + a: 'a', + b: 'b', + c: 'c', + d: 'd', }); }); }); @@ -136,12 +160,16 @@ describe('caps', function () { it('returns invalid argument error if "firstMatch" is not an array and is not undefined (3.2)', function () { for (let arg of [null, 1, true, 'string']) { caps.firstMatch = arg; - (function () { parseCaps(caps); }).should.throw(/must be a JSON array or undefined/); + (function () { + parseCaps(caps); + }.should.throw(/must be a JSON array or undefined/)); } }); it('has "validatedFirstMatchCaps" property that is empty by default if no valid firstMatch caps were found (4)', function () { - parseCaps(caps, {foo: {presence: true}}).validatedFirstMatchCaps.should.deep.equal([]); + parseCaps(caps, { + foo: {presence: true}, + }).validatedFirstMatchCaps.should.deep.equal([]); }); describe('returns a "validatedFirstMatchCaps" array (5)', function () { @@ -159,12 +187,17 @@ describe('caps', function () { caps.alwaysMatch = { 'appium:foo': 'bar', }; - caps.firstMatch = [{ - 'appium:hello': 'world', - }, { - 'appium:goodbye': 'world', - }]; - parseCaps(caps, {goodbye: {presence: true}}).matchedCaps.should.deep.equal({ + caps.firstMatch = [ + { + 'appium:hello': 'world', + }, + { + 'appium:goodbye': 'world', + }, + ]; + parseCaps(caps, { + goodbye: {presence: true}, + }).matchedCaps.should.deep.equal({ foo: 'bar', goodbye: 'world', }); @@ -174,28 +207,28 @@ describe('caps', function () { caps.alwaysMatch = { 'appium:foo': 'bar', }; - caps.firstMatch = [{ - 'appium:hello': 'world', - }, { - 'appium:goodbye': 'world', - }]; + caps.firstMatch = [ + { + 'appium:hello': 'world', + }, + { + 'appium:goodbye': 'world', + }, + ]; should.equal(parseCaps(caps, {someAttribute: {presence: true}}).matchedCaps, null); }); it('that equals firstMatch if firstMatch contains two objects that pass the provided constraints', function () { caps.alwaysMatch = { - 'appium:foo': 'bar' + 'appium:foo': 'bar', }; - caps.firstMatch = [ - {'appium:foo': 'bar1'}, - {'appium:foo': 'bar2'}, - ]; + caps.firstMatch = [{'appium:foo': 'bar1'}, {'appium:foo': 'bar2'}]; let constraints = { foo: { presence: true, isString: true, - } + }, }; parseCaps(caps, constraints).validatedFirstMatchCaps.should.deep.equal(caps.firstMatch); @@ -204,7 +237,9 @@ describe('caps', function () { it('returns no vendor prefix error if the firstMatch[2] does not have it because of no bject', function () { caps.alwaysMatch = {}; caps.firstMatch = [{'appium:foo': 'bar'}, 'foo']; - (() => parseCaps(caps, {})).should.throw(/All non-standard capabilities should have a vendor prefix/); + (() => parseCaps(caps, {})).should.throw( + /All non-standard capabilities should have a vendor prefix/ + ); }); }); @@ -219,12 +254,18 @@ describe('caps', function () { it('merges caps together', function () { caps.firstMatch = [{'appium:foo': 'bar'}]; - parseCaps(caps).matchedCaps.should.deep.equal({hello: 'world', foo: 'bar'}); + parseCaps(caps).matchedCaps.should.deep.equal({ + hello: 'world', + foo: 'bar', + }); }); it('with merged caps', function () { caps.firstMatch = [{'appium:hello': 'bar', 'appium:foo': 'foo'}, {'appium:foo': 'bar'}]; - parseCaps(caps).matchedCaps.should.deep.equal({hello: 'world', foo: 'bar'}); + parseCaps(caps).matchedCaps.should.deep.equal({ + hello: 'world', + foo: 'bar', + }); }); }); }); @@ -237,14 +278,14 @@ describe('caps', function () { it('should return merged caps', function () { processCapabilities({ alwaysMatch: {'appium:hello': 'world'}, - firstMatch: [{'appium:foo': 'bar'}] + firstMatch: [{'appium:foo': 'bar'}], }).should.deep.equal({hello: 'world', foo: 'bar'}); }); it('should strip out the "appium:" prefix for non-standard capabilities', function () { processCapabilities({ alwaysMatch: {'appium:hello': 'world'}, - firstMatch: [{'appium:foo': 'bar'}] + firstMatch: [{'appium:foo': 'bar'}], }).should.deep.equal({hello: 'world', foo: 'bar'}); }); @@ -257,29 +298,33 @@ describe('caps', function () { it('should prefer standard caps that are non-prefixed to prefixed', function () { processCapabilities({ - alwaysMatch: {'appium:platformName': 'Foo', 'platformName': 'Bar'}, - firstMatch: [{'appium:browserName': 'FOO', 'browserName': 'BAR'}], + alwaysMatch: {'appium:platformName': 'Foo', platformName: 'Bar'}, + firstMatch: [{'appium:browserName': 'FOO', browserName: 'BAR'}], }).should.deep.equal({platformName: 'Bar', browserName: 'BAR'}); }); it('should throw exception if duplicates in alwaysMatch and firstMatch', function () { - (() => processCapabilities({ - alwaysMatch: {'platformName': 'Fake', 'appium:fakeCap': 'foobar'}, - firstMatch: [{'appium:platformName': 'bar'}], - })).should.throw(/should not exist on both primary/); + (() => + processCapabilities({ + alwaysMatch: {platformName: 'Fake', 'appium:fakeCap': 'foobar'}, + firstMatch: [{'appium:platformName': 'bar'}], + })).should.throw(/should not exist on both primary/); }); it('should not throw an exception if presence constraint is not met on a firstMatch capability', function () { - const caps = processCapabilities({ - alwaysMatch: {'platformName': 'Fake', 'appium:fakeCap': 'foobar'}, - firstMatch: [{'appium:foo': 'bar'}], - }, { - platformName: { - presence: true, + const caps = processCapabilities( + { + alwaysMatch: {platformName: 'Fake', 'appium:fakeCap': 'foobar'}, + firstMatch: [{'appium:foo': 'bar'}], }, - fakeCap: { - presence: true - }, - }); + { + platformName: { + presence: true, + }, + fakeCap: { + presence: true, + }, + } + ); caps.platformName.should.equal('Fake'); caps.fakeCap.should.equal('foobar'); @@ -287,34 +332,42 @@ describe('caps', function () { }); it('should throw an exception if no matching caps were found', function () { - (() => processCapabilities({ - alwaysMatch: {'platformName': 'Fake', 'appium:fakeCap': 'foobar'}, - firstMatch: [{'appium:foo': 'bar'}], - }, { - platformName: { - presence: true, - }, - fakeCap: { - presence: true - }, - missingCap: { - presence: true, - }, - })).should.throw(/'missingCap' can't be blank/); + (() => + processCapabilities( + { + alwaysMatch: {platformName: 'Fake', 'appium:fakeCap': 'foobar'}, + firstMatch: [{'appium:foo': 'bar'}], + }, + { + platformName: { + presence: true, + }, + fakeCap: { + presence: true, + }, + missingCap: { + presence: true, + }, + } + )).should.throw(/'missingCap' can't be blank/); }); describe('validate Appium constraints', function () { const constraints = {...desiredCapabilityConstraints}; - const expectedMatchingCaps = {'platformName': 'Fake', 'automationName': 'Fake', 'deviceName': 'Fake'}; + const expectedMatchingCaps = { + platformName: 'Fake', + automationName: 'Fake', + deviceName: 'Fake', + }; let matchingCaps; let caps; beforeEach(function () { matchingCaps = { - 'platformName': 'Fake', + platformName: 'Fake', 'appium:automationName': 'Fake', - 'appium:deviceName': 'Fake' + 'appium:deviceName': 'Fake', }; }); @@ -326,7 +379,6 @@ describe('caps', function () { processCapabilities(caps, constraints).should.deep.equal(expectedMatchingCaps); }); - it('should validate when firstMatch[0] has the proper caps', function () { caps = { alwaysMatch: {}, @@ -347,16 +399,15 @@ describe('caps', function () { caps = { alwaysMatch: _.omit(matchingCaps, ['appium:automationName']), }; - processCapabilities(caps, constraints).should.deep.equal(_.omit(expectedMatchingCaps, 'automationName')); + processCapabilities(caps, constraints).should.deep.equal( + _.omit(expectedMatchingCaps, 'automationName') + ); }); it('should pass if first element in "firstMatch" does validate and second element does not', function () { caps = { alwaysMatch: {}, - firstMatch: [ - matchingCaps, - {'appium:badCaps': 'badCaps'}, - ], + firstMatch: [matchingCaps, {'appium:badCaps': 'badCaps'}], }; processCapabilities(caps, constraints).should.deep.equal(expectedMatchingCaps); }); @@ -364,10 +415,7 @@ describe('caps', function () { it('should pass if first element in "firstMatch" does not validate and second element does', function () { caps = { alwaysMatch: {}, - firstMatch: [ - {'appium:badCaps': 'badCaps'}, - matchingCaps, - ], + firstMatch: [{'appium:badCaps': 'badCaps'}, matchingCaps], }; processCapabilities(caps, constraints).should.deep.equal(expectedMatchingCaps); }); @@ -375,55 +423,90 @@ describe('caps', function () { it('should fail when bad parameters are passed in more than one firstMatch capability', function () { caps = { alwaysMatch: {}, - firstMatch: [{ - 'appium:bad': 'params', - }, { - 'appium:more': 'bad-params', - }], + firstMatch: [ + { + 'appium:bad': 'params', + }, + { + 'appium:more': 'bad-params', + }, + ], }; - (() => processCapabilities(caps, constraints)).should.throw(/Could not find matching capabilities/); + (() => processCapabilities(caps, constraints)).should.throw( + /Could not find matching capabilities/ + ); }); }); }); describe('.findNonPrefixedCaps', function () { it('should find alwaysMatch caps with no prefix', function () { - findNonPrefixedCaps({alwaysMatch: { - 'non-standard': 'dummy', - }}).should.eql(['non-standard']); + findNonPrefixedCaps({ + alwaysMatch: { + 'non-standard': 'dummy', + }, + }).should.eql(['non-standard']); }); it('should not find a standard cap in alwaysMatch', function () { - findNonPrefixedCaps({alwaysMatch: { - 'platformName': 'Any', - }}).should.eql([]); + findNonPrefixedCaps({ + alwaysMatch: { + platformName: 'Any', + }, + }).should.eql([]); }); it('should find firstMatch caps with no prefix', function () { - findNonPrefixedCaps({alwaysMatch: {}, firstMatch: [{ - 'non-standard': 'dummy', - }]}).should.eql(['non-standard']); + findNonPrefixedCaps({ + alwaysMatch: {}, + firstMatch: [ + { + 'non-standard': 'dummy', + }, + ], + }).should.eql(['non-standard']); }); it('should not find a standard cap in prefix', function () { - findNonPrefixedCaps({alwaysMatch: {}, firstMatch: [{ - 'platformName': 'Any', - }]}).should.eql([]); + findNonPrefixedCaps({ + alwaysMatch: {}, + firstMatch: [ + { + platformName: 'Any', + }, + ], + }).should.eql([]); }); it('should find firstMatch caps in second item of firstMatch array', function () { - findNonPrefixedCaps({alwaysMatch: {}, firstMatch: [{}, { - 'non-standard': 'dummy', - }]}).should.eql(['non-standard']); + findNonPrefixedCaps({ + alwaysMatch: {}, + firstMatch: [ + {}, + { + 'non-standard': 'dummy', + }, + ], + }).should.eql(['non-standard']); }); it('should remove duplicates from alwaysMatch and firstMatch', function () { - findNonPrefixedCaps({alwaysMatch: { - 'non-standard': 'something', - }, firstMatch: [{ - 'non-standard': 'dummy', - }]}).should.eql(['non-standard']); + findNonPrefixedCaps({ + alwaysMatch: { + 'non-standard': 'something', + }, + firstMatch: [ + { + 'non-standard': 'dummy', + }, + ], + }).should.eql(['non-standard']); }); it('should remove duplicates from firstMatch', function () { - findNonPrefixedCaps({firstMatch: [{ - 'non-standard': 'dummy', - }, { - 'non-standard': 'dummy 2', - }]}).should.eql(['non-standard']); + findNonPrefixedCaps({ + firstMatch: [ + { + 'non-standard': 'dummy', + }, + { + 'non-standard': 'dummy 2', + }, + ], + }).should.eql(['non-standard']); }); it('should remove duplicates and keep standard capabilities', function () { const alwaysMatch = { @@ -432,10 +515,25 @@ describe('caps', function () { nonStandardTwo: 'non-standard', }; const firstMatch = [ - {nonStandardThree: 'non-standard', nonStandardFour: 'non-standard', browserName: 'FakeBrowser'}, - {nonStandardThree: 'non-standard', nonStandardFour: 'non-standard', nonStandardFive: 'non-standard', browserVersion: 'whateva'}, + { + nonStandardThree: 'non-standard', + nonStandardFour: 'non-standard', + browserName: 'FakeBrowser', + }, + { + nonStandardThree: 'non-standard', + nonStandardFour: 'non-standard', + nonStandardFive: 'non-standard', + browserVersion: 'whateva', + }, ]; - findNonPrefixedCaps({alwaysMatch, firstMatch}).should.eql(['nonStandardOne', 'nonStandardTwo', 'nonStandardThree', 'nonStandardFour', 'nonStandardFive']); + findNonPrefixedCaps({alwaysMatch, firstMatch}).should.eql([ + 'nonStandardOne', + 'nonStandardTwo', + 'nonStandardThree', + 'nonStandardFour', + 'nonStandardFive', + ]); }); }); @@ -463,7 +561,7 @@ describe('caps', function () { ...nonAppiumCaps, [APPIUM_OPTS_CAP]: { ...appiumUnprefixedCaps, - } + }, }; promoteAppiumOptions(caps).should.eql(simpleCaps); }); @@ -472,7 +570,7 @@ describe('caps', function () { ...nonAppiumCaps, [APPIUM_OPTS_CAP]: { ...appiumCaps, - } + }, }; promoteAppiumOptions(caps).should.eql(simpleCaps); }); @@ -497,7 +595,7 @@ describe('caps', function () { [APPIUM_OPTS_CAP]: { ...appiumCaps, foo: 'baz', - } + }, }; promoteAppiumOptions(caps).should.eql({ ...simpleCaps, @@ -509,7 +607,10 @@ describe('caps', function () { describe('#isW3cCaps', function () { it('should drop invalid W3C capabilities', function () { for (const invalidCaps of [ - null, undefined, [], {}, + null, + undefined, + [], + {}, {firstMatch: null}, {firtMatch: [{}]}, {alwaysMatch: null}, diff --git a/packages/base-driver/test/unit/basedriver/capability.spec.js b/packages/base-driver/test/unit/basedriver/capability.spec.js index f06bc9d65..000259695 100644 --- a/packages/base-driver/test/unit/basedriver/capability.spec.js +++ b/packages/base-driver/test/unit/basedriver/capability.spec.js @@ -1,9 +1,9 @@ // @ts-check import B from 'bluebird'; -import { default as BaseDriver, errors } from '../../../lib'; +import {default as BaseDriver, errors} from '../../../lib'; import logger from '../../../lib/basedriver/logger'; -import { createSandbox } from 'sinon'; +import {createSandbox} from 'sinon'; describe('Desired Capabilities', function () { let d; @@ -21,76 +21,83 @@ describe('Desired Capabilities', function () { }); it('should require platformName and deviceName', async function () { - await d.createSession({ - firstMatch: [{}] - }).should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /platformName/); + await d + .createSession({ + firstMatch: [{}], + }) + .should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /platformName/); }); - it('should require platformName', async function () { - await d.createSession({ - alwaysMatch: { - 'appium:deviceName': 'Delorean' - } - }).should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /platformName/); + await d + .createSession({ + alwaysMatch: { + 'appium:deviceName': 'Delorean', + }, + }) + .should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /platformName/); }); it('should not care about cap order', async function () { await d.createSession(null, null, { alwaysMatch: { 'appium:deviceName': 'Delorean', - platformName: 'iOS' - } + platformName: 'iOS', + }, }).should.eventually.be.fulfilled; }); it('should check required caps which are added to driver', async function () { d.desiredCapConstraints = { necessary: { - presence: true + presence: true, }, proper: { presence: true, isString: true, - inclusion: ['Delorean', 'Reventon'] - } + inclusion: ['Delorean', 'Reventon'], + }, }; - await d.createSession(null, null, { - alwaysMatch: { - 'platformName': 'iOS', - 'appium:deviceName': 'Delorean' - } - }).should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /necessary.*proper/); + await d + .createSession(null, null, { + alwaysMatch: { + platformName: 'iOS', + 'appium:deviceName': 'Delorean', + }, + }) + .should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /necessary.*proper/); }); it('should check added required caps in addition to base', async function () { d.desiredCapConstraints = { necessary: { - presence: true + presence: true, }, proper: { presence: true, isString: true, - inclusion: ['Delorean', 'Reventon'] - } + inclusion: ['Delorean', 'Reventon'], + }, }; - await d.createSession(null, null, { - alwaysMatch: { - 'appium:necessary': 'yup' - } - }).should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /platformName/); + await d + .createSession(null, null, { + alwaysMatch: { + 'appium:necessary': 'yup', + }, + }) + .should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /platformName/); }); it('should accept extra capabilities', async function () { await d.createSession(null, null, { alwaysMatch: { - 'platformName': 'iOS', + platformName: 'iOS', 'appium:deviceName': 'Delorean', 'appium:extra': 'cheese', - 'appium:hold the': 'sauce' - } + 'appium:hold the': 'sauce', + }, }).should.eventually.be.fulfilled; }); @@ -99,33 +106,35 @@ describe('Desired Capabilities', function () { await d.createSession(null, null, { alwaysMatch: { - 'platformName': 'iOS', + platformName: 'iOS', 'appium:deviceName': 'Delorean', 'appium:extra': 'cheese', - 'appium:hold the': 'sauce' - } + 'appium:hold the': 'sauce', + }, }); d.log.warn.should.have.been.called; }); it('should be sensitive to the case of caps', async function () { - await d.createSession(null, null, { - alwaysMatch: { - 'platformname': 'iOS', - 'appium:deviceName': 'Delorean' - } - }).should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /platformName/); + await d + .createSession(null, null, { + alwaysMatch: { + platformname: 'iOS', + 'appium:deviceName': 'Delorean', + }, + }) + .should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /platformName/); }); describe('boolean capabilities', function () { it('should allow a string "false"', async function () { await d.createSession(null, null, { alwaysMatch: { - 'platformName': 'iOS', + platformName: 'iOS', 'appium:deviceName': 'Delorean', - 'appium:noReset': 'false' - } + 'appium:noReset': 'false', + }, }); d.log.warn.should.have.been.called; @@ -136,10 +145,10 @@ describe('Desired Capabilities', function () { it('should allow a string "true"', async function () { await d.createSession(null, null, { alwaysMatch: { - 'platformName': 'iOS', + platformName: 'iOS', 'appium:deviceName': 'Delorean', - 'appium:noReset': 'true' - } + 'appium:noReset': 'true', + }, }); d.log.warn.should.have.been.called; @@ -150,10 +159,10 @@ describe('Desired Capabilities', function () { it('should allow a string "true" in string capabilities', async function () { await d.createSession(null, null, { alwaysMatch: { - 'platformName': 'iOS', + platformName: 'iOS', 'appium:deviceName': 'Delorean', - 'appium:language': 'true' - } + 'appium:language': 'true', + }, }); d.log.warn.should.not.have.been.called; @@ -166,10 +175,10 @@ describe('Desired Capabilities', function () { it('should allow a string "1"', async function () { await d.createSession(null, null, { alwaysMatch: { - 'platformName': 'iOS', + platformName: 'iOS', 'appium:deviceName': 'Delorean', - 'appium:newCommandTimeout': '1' - } + 'appium:newCommandTimeout': '1', + }, }); d.log.warn.should.have.been.called; @@ -180,10 +189,10 @@ describe('Desired Capabilities', function () { it('should allow a string "1.1"', async function () { await d.createSession(null, null, { alwaysMatch: { - 'platformName': 'iOS', + platformName: 'iOS', 'appium:deviceName': 'Delorean', - 'appium:newCommandTimeout': '1.1' - } + 'appium:newCommandTimeout': '1.1', + }, }); d.log.warn.should.have.been.called; @@ -194,10 +203,10 @@ describe('Desired Capabilities', function () { it('should allow a string "1" in string capabilities', async function () { await d.createSession(null, null, { alwaysMatch: { - 'platformName': 'iOS', + platformName: 'iOS', 'appium:deviceName': 'Delorean', - 'appium:language': '1' - } + 'appium:language': '1', + }, }); d.log.warn.should.not.have.been.called; @@ -206,13 +215,15 @@ describe('Desired Capabilities', function () { }); }); - it ('should error if objects in caps', async function () { - await d.createSession(null, null, { - alwaysMatch: { - 'platformName': {a: 'iOS'}, - 'appium:deviceName': 'Delorean' - } - }).should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /platformName/i); + it('should error if objects in caps', async function () { + await d + .createSession(null, null, { + alwaysMatch: { + platformName: {a: 'iOS'}, + 'appium:deviceName': 'Delorean', + }, + }) + .should.eventually.be.rejectedWith(errors.SessionNotCreatedError, /platformName/i); }); it('should check for deprecated caps', async function () { @@ -220,16 +231,16 @@ describe('Desired Capabilities', function () { d.desiredCapConstraints = { 'lynx-version': { - deprecated: true - } + deprecated: true, + }, }; await d.createSession(null, null, { alwaysMatch: { - 'platformName': 'iOS', + platformName: 'iOS', 'appium:deviceName': 'Delorean', - 'appium:lynx-version': 5 - } + 'appium:lynx-version': 5, + }, }); logger.warn.should.have.been.called; @@ -240,16 +251,16 @@ describe('Desired Capabilities', function () { d.desiredCapConstraints = { 'lynx-version': { - deprecated: false - } + deprecated: false, + }, }; await d.createSession(null, null, { alwaysMatch: { - 'platformName': 'iOS', + platformName: 'iOS', 'appium:deviceName': 'Delorean', - 'appium:lynx-version': 5 - } + 'appium:lynx-version': 5, + }, }); d.log.warn.should.not.have.been.called; @@ -257,9 +268,9 @@ describe('Desired Capabilities', function () { it('should not validate against null/undefined caps', async function () { d.desiredCapConstraints = { - 'foo': { - isString: true - } + foo: { + isString: true, + }, }; try { @@ -267,28 +278,30 @@ describe('Desired Capabilities', function () { alwaysMatch: { platformName: 'iOS', 'appium:deviceName': 'Dumb', - 'appium:foo': null - } + 'appium:foo': null, + }, }); } finally { await d.deleteSession(); } - await d.createSession(null, null, { - alwaysMatch: { - platformName: 'iOS', - 'appium:deviceName': 'Dumb', - 'appium:foo': 1 - } - }).should.eventually.be.rejectedWith(/'foo' must be of type string/); + await d + .createSession(null, null, { + alwaysMatch: { + platformName: 'iOS', + 'appium:deviceName': 'Dumb', + 'appium:foo': 1, + }, + }) + .should.eventually.be.rejectedWith(/'foo' must be of type string/); try { await d.createSession(null, null, { alwaysMatch: { platformName: 'iOS', 'appium:deviceName': 'Dumb', - 'appium:foo': undefined - } + 'appium:foo': undefined, + }, }); } finally { await d.deleteSession(); @@ -299,8 +312,8 @@ describe('Desired Capabilities', function () { alwaysMatch: { platformName: 'iOS', 'appium:deviceName': 'Dumb', - 'appium:foo': '' - } + 'appium:foo': '', + }, }); } finally { await d.deleteSession(); @@ -310,49 +323,61 @@ describe('Desired Capabilities', function () { it('should still validate null/undefined/empty caps whose presence is required', async function () { d.desiredCapConstraints = { foo: { - presence: true + presence: true, }, }; - await d.createSession(null, null, { - alwaysMatch: { - platformName: 'iOS', - 'appium:deviceName': 'Dumb', - 'appium:foo': null - } - }).should.eventually.be.rejectedWith(/blank/); + await d + .createSession(null, null, { + alwaysMatch: { + platformName: 'iOS', + 'appium:deviceName': 'Dumb', + 'appium:foo': null, + }, + }) + .should.eventually.be.rejectedWith(/blank/); - await d.createSession(null, { - alwaysMatch: { - platformName: 'iOS', - 'appium:deviceName': 'Dumb', - 'appium:foo': '' - } - }).should.eventually.be.rejectedWith(/blank/); + await d + .createSession(null, { + alwaysMatch: { + platformName: 'iOS', + 'appium:deviceName': 'Dumb', + 'appium:foo': '', + }, + }) + .should.eventually.be.rejectedWith(/blank/); - await d.createSession({ - firstMatch: [{ - platformName: 'iOS', - 'appium:deviceName': 'Dumb', - 'appium:foo': {} - }] - }).should.eventually.be.rejectedWith(/blank/); + await d + .createSession({ + firstMatch: [ + { + platformName: 'iOS', + 'appium:deviceName': 'Dumb', + 'appium:foo': {}, + }, + ], + }) + .should.eventually.be.rejectedWith(/blank/); - await d.createSession({ - alwaysMatch: { - platformName: 'iOS', - 'appium:deviceName': 'Dumb', - 'appium:foo': [] - } - }).should.eventually.be.rejectedWith(/blank/); + await d + .createSession({ + alwaysMatch: { + platformName: 'iOS', + 'appium:deviceName': 'Dumb', + 'appium:foo': [], + }, + }) + .should.eventually.be.rejectedWith(/blank/); - await d.createSession({ - alwaysMatch: { - platformName: 'iOS', - 'appium:deviceName': 'Dumb', - 'appium:foo': ' ' - } - }).should.eventually.be.rejectedWith(/blank/); + await d + .createSession({ + alwaysMatch: { + platformName: 'iOS', + 'appium:deviceName': 'Dumb', + 'appium:foo': ' ', + }, + }) + .should.eventually.be.rejectedWith(/blank/); }); describe('w3c', function () { @@ -360,8 +385,9 @@ describe('Desired Capabilities', function () { const [sessionId, caps] = await d.createSession(null, null, { alwaysMatch: { platformName: 'iOS', - 'appium:deviceName': 'Delorean' - }, firstMatch: [{}], + 'appium:deviceName': 'Delorean', + }, + firstMatch: [{}], }); try { sessionId.should.exist; @@ -377,7 +403,11 @@ describe('Desired Capabilities', function () { it('should raise an error if w3c capabilities is not a plain JSON object', async function () { const testValues = [true, 'string', [], 100]; // this loop runs in parallel, and does not guarantee all assertions will be made - await B.map(testValues, (val) => d.createSession(null, null, val).should.eventually.be.rejectedWith(errors.SessionNotCreatedError)); + await B.map(testValues, (val) => + d + .createSession(null, null, val) + .should.eventually.be.rejectedWith(errors.SessionNotCreatedError) + ); }); }); }); diff --git a/packages/base-driver/test/unit/basedriver/commands/event.spec.js b/packages/base-driver/test/unit/basedriver/commands/event.spec.js index fbb3062e1..58b4e9d52 100644 --- a/packages/base-driver/test/unit/basedriver/commands/event.spec.js +++ b/packages/base-driver/test/unit/basedriver/commands/event.spec.js @@ -1,7 +1,5 @@ import _ from 'lodash'; -import { BaseDriver } from '../../../../lib'; - - +import {BaseDriver} from '../../../../lib'; describe('logging custom events', function () { it('should allow logging of events', async function () { @@ -26,7 +24,8 @@ describe('#getLogEvents', function () { d._eventHistory.should.eql({commands: []}); d._eventHistory.testCommand = ['1', '2', '3']; (await d.getLogEvents()).should.eql({ - commands: [], testCommand: ['1', '2', '3'] + commands: [], + testCommand: ['1', '2', '3'], }); }); @@ -35,7 +34,7 @@ describe('#getLogEvents', function () { d._eventHistory.should.eql({commands: []}); d._eventHistory.testCommand = ['1', '2', '3']; (await d.getLogEvents('testCommand')).should.eql({ - testCommand: ['1', '2', '3'] + testCommand: ['1', '2', '3'], }); }); @@ -52,7 +51,8 @@ describe('#getLogEvents', function () { d._eventHistory.testCommand = ['1', '2', '3']; d._eventHistory.testCommand2 = ['4', '5']; (await d.getLogEvents(['testCommand', 'testCommand2'])).should.eql({ - testCommand: ['1', '2', '3'], testCommand2: ['4', '5'] + testCommand: ['1', '2', '3'], + testCommand2: ['4', '5'], }); }); @@ -61,7 +61,7 @@ describe('#getLogEvents', function () { d._eventHistory.should.eql({commands: []}); d._eventHistory['custom:appiumEvent'] = ['1', '2', '3']; (await d.getLogEvents(['custom:appiumEvent'])).should.eql({ - 'custom:appiumEvent': ['1', '2', '3'] + 'custom:appiumEvent': ['1', '2', '3'], }); }); diff --git a/packages/base-driver/test/unit/basedriver/commands/log.spec.js b/packages/base-driver/test/unit/basedriver/commands/log.spec.js index 263a53463..5c8a5a87f 100644 --- a/packages/base-driver/test/unit/basedriver/commands/log.spec.js +++ b/packages/base-driver/test/unit/basedriver/commands/log.spec.js @@ -1,6 +1,6 @@ // @ts-check import {LogMixin} from '../../../../lib/basedriver/commands/log'; -import { createSandbox } from 'sinon'; +import {createSandbox} from 'sinon'; import _ from 'lodash'; const expect = chai.expect; @@ -28,11 +28,19 @@ describe('log commands -', function () { beforeEach(function () { sandbox = createSandbox(); // @ts-expect-error - LogCommands = LogMixin(class { get log () { return this._log; }}); + LogCommands = LogMixin( + class { + get log() { + return this._log; + } + } + ); logCommands = new LogCommands(); // reset the supported log types logCommands.supportedLogTypes = {}; - logCommands._log = /** @type {import('@appium/types').AppiumLogger} */({debug: _.noop}); + logCommands._log = /** @type {import('@appium/types').AppiumLogger} */ ({ + debug: _.noop, + }); }); afterEach(function () { @@ -71,7 +79,9 @@ describe('log commands -', function () { err = _err; } expect(err).to.exist; - err.message.should.eql(`Unsupported log type 'three'. Supported types: {"one":"First logs","two":"Seconds logs"}`); + err.message.should.eql( + `Unsupported log type 'three'. Supported types: {"one":"First logs","two":"Seconds logs"}` + ); one.called.should.be.false; two.called.should.be.false; }); diff --git a/packages/base-driver/test/unit/basedriver/device-settings.spec.js b/packages/base-driver/test/unit/basedriver/device-settings.spec.js index c45c12276..182a06585 100644 --- a/packages/base-driver/test/unit/basedriver/device-settings.spec.js +++ b/packages/base-driver/test/unit/basedriver/device-settings.spec.js @@ -1,7 +1,7 @@ -import { node } from '@appium/support'; +import {node} from '@appium/support'; import sinon from 'sinon'; -import { DeviceSettings, MAX_SETTINGS_SIZE } from '../../../lib/basedriver/device-settings'; -import { InvalidArgumentError } from '../../../lib/protocol/errors'; +import {DeviceSettings, MAX_SETTINGS_SIZE} from '../../../lib/basedriver/device-settings'; +import {InvalidArgumentError} from '../../../lib/protocol/errors'; const {expect} = chai; @@ -41,7 +41,8 @@ describe('DeviceSettings', function () { it('should reject with an InvalidArgumentError', async function () { const deviceSettings = new DeviceSettings(); await expect(deviceSettings.update()).to.be.rejectedWith( - InvalidArgumentError, /with valid JSON/i + InvalidArgumentError, + /with valid JSON/i ); }); }); @@ -50,7 +51,8 @@ describe('DeviceSettings', function () { it('should reject with an InvalidArgumentError', async function () { const deviceSettings = new DeviceSettings(); await expect(deviceSettings.update(null)).to.be.rejectedWith( - InvalidArgumentError, /with valid JSON/i + InvalidArgumentError, + /with valid JSON/i ); }); }); @@ -64,7 +66,8 @@ describe('DeviceSettings', function () { it('should reject with an InvalidArgumentError', async function () { const deviceSettings = new DeviceSettings(); await expect(deviceSettings.update({stuff: 'things'})).to.be.rejectedWith( - InvalidArgumentError, /object size exceeds/i + InvalidArgumentError, + /object size exceeds/i ); }); }); @@ -88,7 +91,11 @@ describe('DeviceSettings', function () { it('should call the `_onSettingsUpdate` listener', async function () { const deviceSettings = new DeviceSettings({}, onSettingsUpdate); await deviceSettings.update({stuff: 'things'}); - expect(onSettingsUpdate).to.have.been.calledOnceWithExactly('stuff', 'things', undefined); + expect(onSettingsUpdate).to.have.been.calledOnceWithExactly( + 'stuff', + 'things', + undefined + ); }); }); }); diff --git a/packages/base-driver/test/unit/basedriver/driver.spec.js b/packages/base-driver/test/unit/basedriver/driver.spec.js index da5482313..0dd269170 100644 --- a/packages/base-driver/test/unit/basedriver/driver.spec.js +++ b/packages/base-driver/test/unit/basedriver/driver.spec.js @@ -1,9 +1,9 @@ // transpile:mocha import BaseDriver from '../../../lib'; -import { baseDriverUnitTests } from '../../basedriver'; +import {baseDriverUnitTests} from '../../basedriver'; baseDriverUnitTests(BaseDriver, { platformName: 'iOS', - 'appium:deviceName': 'Delorean' + 'appium:deviceName': 'Delorean', }); diff --git a/packages/base-driver/test/unit/basedriver/helpers.spec.js b/packages/base-driver/test/unit/basedriver/helpers.spec.js index 3b7dd795d..954d9c875 100644 --- a/packages/base-driver/test/unit/basedriver/helpers.spec.js +++ b/packages/base-driver/test/unit/basedriver/helpers.spec.js @@ -1,8 +1,12 @@ -import { zip, fs, tempDir } from '@appium/support'; -import { configureApp, isPackageOrBundle, duplicateKeys, parseCapsArray } from '../../../lib/basedriver/helpers'; +import {zip, fs, tempDir} from '@appium/support'; +import { + configureApp, + isPackageOrBundle, + duplicateKeys, + parseCapsArray, +} from '../../../lib/basedriver/helpers'; import sinon from 'sinon'; - describe('helpers', function () { describe('#isPackageOrBundle', function () { it('should accept packages and bundles', function () { @@ -17,37 +21,43 @@ describe('helpers', function () { describe('#duplicateKeys', function () { it('should translate key in an object', function () { - duplicateKeys({'foo': 'hello world'}, 'foo', 'bar').should.eql({'foo': 'hello world', 'bar': 'hello world'}); + duplicateKeys({foo: 'hello world'}, 'foo', 'bar').should.eql({ + foo: 'hello world', + bar: 'hello world', + }); }); it('should translate key in an object within an object', function () { - duplicateKeys({'key': {'foo': 'hello world'}}, 'foo', 'bar').should.eql({'key': {'foo': 'hello world', 'bar': 'hello world'}}); + duplicateKeys({key: {foo: 'hello world'}}, 'foo', 'bar').should.eql({ + key: {foo: 'hello world', bar: 'hello world'}, + }); }); it('should translate key in an object with an array', function () { - duplicateKeys([ - {'key': {'foo': 'hello world'}}, - {'foo': 'HELLO WORLD'} - ], 'foo', 'bar').should.eql([ - {'key': {'foo': 'hello world', 'bar': 'hello world'}}, - {'foo': 'HELLO WORLD', 'bar': 'HELLO WORLD'} + duplicateKeys([{key: {foo: 'hello world'}}, {foo: 'HELLO WORLD'}], 'foo', 'bar').should.eql([ + {key: {foo: 'hello world', bar: 'hello world'}}, + {foo: 'HELLO WORLD', bar: 'HELLO WORLD'}, ]); }); it('should duplicate both keys', function () { - duplicateKeys({ - 'keyOne': { - 'foo': 'hello world', + duplicateKeys( + { + keyOne: { + foo: 'hello world', + }, + keyTwo: { + bar: 'HELLO WORLD', + }, }, - 'keyTwo': { - 'bar': 'HELLO WORLD', + 'foo', + 'bar' + ).should.eql({ + keyOne: { + foo: 'hello world', + bar: 'hello world', }, - }, 'foo', 'bar').should.eql({ - 'keyOne': { - 'foo': 'hello world', - 'bar': 'hello world', + keyTwo: { + bar: 'HELLO WORLD', + foo: 'HELLO WORLD', }, - 'keyTwo': { - 'bar': 'HELLO WORLD', - 'foo': 'HELLO WORLD', - } }); }); it('should not do anything to primitives', function () { @@ -57,34 +67,34 @@ describe('helpers', function () { }); it('should rename keys on big complex objects', function () { const input = [ - {'foo': 'bar'}, + {foo: 'bar'}, { hello: { world: { - 'foo': 'BAR', - } - }, - foo: 'bahr' - }, - 'foo', - null, - 0 - ]; - const expectedOutput = [ - {'foo': 'bar', 'FOO': 'bar'}, - { - hello: { - world: { - 'foo': 'BAR', - 'FOO': 'BAR', - } + foo: 'BAR', + }, }, foo: 'bahr', - FOO: 'bahr' }, 'foo', null, - 0 + 0, + ]; + const expectedOutput = [ + {foo: 'bar', FOO: 'bar'}, + { + hello: { + world: { + foo: 'BAR', + FOO: 'BAR', + }, + }, + foo: 'bahr', + FOO: 'bahr', + }, + 'foo', + null, + 0, ]; duplicateKeys(input, 'foo', 'FOO').should.deep.equal(expectedOutput); }); @@ -128,7 +138,7 @@ describe('parseCapsArray', function () { parseCapsArray('["/tmp/my/app.zip"]').should.eql(['/tmp/my/app.zip']); parseCapsArray('["/tmp/my/app.zip","/tmp/my/app2.zip"]').should.eql([ '/tmp/my/app.zip', - '/tmp/my/app2.zip' + '/tmp/my/app2.zip', ]); }); it('should return an array without change', function () { diff --git a/packages/base-driver/test/unit/basedriver/timeout.spec.js b/packages/base-driver/test/unit/basedriver/timeout.spec.js index 384763c55..252d39cb5 100644 --- a/packages/base-driver/test/unit/basedriver/timeout.spec.js +++ b/packages/base-driver/test/unit/basedriver/timeout.spec.js @@ -1,7 +1,7 @@ // @ts-check import BaseDriver from '../../../lib'; -import { createSandbox } from 'sinon'; +import {createSandbox} from 'sinon'; describe('timeout', function () { let driver = new BaseDriver(); @@ -28,10 +28,12 @@ describe('timeout', function () { await driver.timeouts('random timeout', 42).should.eventually.be.rejected; }); it('should throw an error if something random is sent to scriptDuration', async function () { - await driver.timeouts(undefined, undefined, 123, undefined, undefined).should.eventually.be.rejected; + await driver.timeouts(undefined, undefined, 123, undefined, undefined).should.eventually.be + .rejected; }); it('should throw an error if something random is sent to pageLoadDuration', async function () { - await driver.timeouts(undefined, undefined, undefined, 123, undefined).should.eventually.be.rejected; + await driver.timeouts(undefined, undefined, undefined, 123, undefined).should.eventually.be + .rejected; }); }); describe('implicit wait', function () { diff --git a/packages/base-driver/test/unit/express/server.spec.js b/packages/base-driver/test/unit/express/server.spec.js index d47d9fc03..f18962cb1 100644 --- a/packages/base-driver/test/unit/express/server.spec.js +++ b/packages/base-driver/test/unit/express/server.spec.js @@ -1,14 +1,14 @@ // transpile:mocha -import { server, routeConfiguringFunction } from '../../../lib'; -import { configureServer, normalizeBasePath } from '../../../lib/express/server'; -import { createSandbox } from 'sinon'; -import { getTestPort } from '../../helpers'; +import {server, routeConfiguringFunction} from '../../../lib'; +import {configureServer, normalizeBasePath} from '../../../lib/express/server'; +import {createSandbox} from 'sinon'; +import {getTestPort} from '../../helpers'; const newMethodMap = { '/session/:sessionId/fake': { GET: {command: 'fakeGet'}, - POST: {command: 'fakePost', payloadParams: {required: ['fakeParam']}} + POST: {command: 'fakePost', payloadParams: {required: ['fakeParam']}}, }, }; @@ -17,7 +17,7 @@ const updateServer = (app, httpServer) => { httpServer.updated = true; }; -function fakeDriver () { +function fakeDriver() { return {sessionExists: () => {}, executeCommand: () => {}}; } @@ -26,17 +26,19 @@ describe('server configuration', function () { let sandbox; - function fakeApp () { + function fakeApp() { const app = { use: sandbox.spy(), all: sandbox.spy(), get: sandbox.spy(), post: sandbox.spy(), delete: sandbox.spy(), - totalCount: () => ( - app.use.callCount + app.all.callCount + app.get.callCount + app.post.callCount + - app.delete.callCount - ) + totalCount: () => + app.use.callCount + + app.all.callCount + + app.get.callCount + + app.post.callCount + + app.delete.callCount, }; return app; } @@ -87,7 +89,7 @@ describe('server configuration', function () { routeConfiguringFunction: routeConfiguringFunction(driver), port, extraMethodMap: newMethodMap, - serverUpdaters: [updateServer] + serverUpdaters: [updateServer], }); try { _server.updated.should.be.true; diff --git a/packages/base-driver/test/unit/express/static.spec.js b/packages/base-driver/test/unit/express/static.spec.js index 0cc803ec0..313e93ecc 100644 --- a/packages/base-driver/test/unit/express/static.spec.js +++ b/packages/base-driver/test/unit/express/static.spec.js @@ -1,7 +1,7 @@ // transpile:mocha -import { welcome } from '../../../lib/express/static'; -import { createSandbox } from 'sinon'; +import {welcome} from '../../../lib/express/static'; +import {createSandbox} from 'sinon'; describe('welcome', function () { let sandbox; @@ -16,11 +16,11 @@ describe('welcome', function () { it('should fill the template', async function () { let res = { - send: sandbox.stub() + send: sandbox.stub(), }; await welcome({}, res); res.send.calledOnce.should.be.true; - res.send.args[0][0].should.include('Let\'s browse!'); + res.send.args[0][0].should.include("Let's browse!"); }); }); diff --git a/packages/base-driver/test/unit/jsonwp-proxy/mock-request.js b/packages/base-driver/test/unit/jsonwp-proxy/mock-request.js index 8d45a6083..005eb438c 100644 --- a/packages/base-driver/test/unit/jsonwp-proxy/mock-request.js +++ b/packages/base-driver/test/unit/jsonwp-proxy/mock-request.js @@ -1,4 +1,4 @@ -function resFixture (url, method) { +function resFixture(url, method) { if (/\/status$/.test(url)) { return [200, {status: 0, value: {foo: 'bar'}}]; } @@ -20,7 +20,8 @@ function resFixture (url, method) { throw new Error("Can't handle url " + url); } -async function request (opts) { // eslint-disable-line require-await +// eslint-disable-next-line require-await +async function request(opts) { const {url, method, json} = opts; if (/badurl$/.test(url)) { throw new Error('noworky'); diff --git a/packages/base-driver/test/unit/jsonwp-proxy/protocol-converter.spec.js b/packages/base-driver/test/unit/jsonwp-proxy/protocol-converter.spec.js index fef7f4bad..6fbe3a86b 100644 --- a/packages/base-driver/test/unit/jsonwp-proxy/protocol-converter.spec.js +++ b/packages/base-driver/test/unit/jsonwp-proxy/protocol-converter.spec.js @@ -1,10 +1,11 @@ import _ from 'lodash'; -import { PROTOCOLS } from '../../../lib/constants'; -import ProtocolConverter, {COMMAND_URLS_CONFLICTS} from '../../../lib/jsonwp-proxy/protocol-converter'; +import {PROTOCOLS} from '../../../lib/constants'; +import ProtocolConverter, { + COMMAND_URLS_CONFLICTS, +} from '../../../lib/jsonwp-proxy/protocol-converter'; const {MJSONWP, W3C} = PROTOCOLS; - describe('Protocol Converter', function () { describe('getTimeoutRequestObjects', function () { let converter; @@ -30,7 +31,11 @@ describe('Protocol Converter', function () { }); it('should take multiple W3C timeouts and produce multiple MJSONWP compatible objects', function () { converter.downstreamProtocol = MJSONWP; - let [scriptTimeout, pageLoadTimeout, implicitTimeout] = converter.getTimeoutRequestObjects({script: 100, pageLoad: 200, implicit: 300}); + let [scriptTimeout, pageLoadTimeout, implicitTimeout] = converter.getTimeoutRequestObjects({ + script: 100, + pageLoad: 200, + implicit: 300, + }); scriptTimeout.should.eql({ type: 'script', ms: 100, @@ -46,19 +51,28 @@ describe('Protocol Converter', function () { }); it('should take MJSONWP input and produce W3C compatible object', function () { converter.downstreamProtocol = W3C; - let timeoutObjects = converter.getTimeoutRequestObjects({type: 'implicit', ms: 300}); + let timeoutObjects = converter.getTimeoutRequestObjects({ + type: 'implicit', + ms: 300, + }); timeoutObjects.length.should.equal(1); timeoutObjects[0].should.eql({implicit: 300}); }); it('should not change the input if protocol name is unknown', function () { converter.downstreamProtocol = null; - let timeoutObjects = converter.getTimeoutRequestObjects({type: 'implicit', ms: 300}); + let timeoutObjects = converter.getTimeoutRequestObjects({ + type: 'implicit', + ms: 300, + }); timeoutObjects.length.should.equal(1); timeoutObjects[0].should.eql({type: 'implicit', ms: 300}); }); it('should not change the input if protocol name is unchanged', function () { converter.downstreamProtocol = MJSONWP; - let timeoutObjects = converter.getTimeoutRequestObjects({type: 'implicit', ms: 300}); + let timeoutObjects = converter.getTimeoutRequestObjects({ + type: 'implicit', + ms: 300, + }); timeoutObjects.length.should.equal(1); timeoutObjects[0].should.eql({type: 'implicit', ms: 300}); }); @@ -117,14 +131,22 @@ describe('Protocol Converter', function () { } }); it('should convert "property/value" to "attribute/value"', function () { - jsonwpConverter('/session/123/element/456/property/value').should.equal('/session/123/element/456/attribute/value'); + jsonwpConverter('/session/123/element/456/property/value').should.equal( + '/session/123/element/456/attribute/value' + ); }); it('should convert "property/:somePropName" to "attribute/:somePropName"', function () { - jsonwpConverter('/session/123/element/456/property/somePropName').should.equal('/session/123/element/456/attribute/somePropName'); + jsonwpConverter('/session/123/element/456/property/somePropName').should.equal( + '/session/123/element/456/attribute/somePropName' + ); }); it('should not convert from JSONWP to W3C', function () { - w3cConverter('/session/123/element/456/attribute/someAttr').should.equal('/session/123/element/456/attribute/someAttr'); - w3cConverter('/session/123/element/456/property/someProp').should.equal('/session/123/element/456/property/someProp'); + w3cConverter('/session/123/element/456/attribute/someAttr').should.equal( + '/session/123/element/456/attribute/someAttr' + ); + w3cConverter('/session/123/element/456/property/someProp').should.equal( + '/session/123/element/456/property/someProp' + ); }); }); }); diff --git a/packages/base-driver/test/unit/jsonwp-proxy/proxy.spec.js b/packages/base-driver/test/unit/jsonwp-proxy/proxy.spec.js index c31d45ac3..8e82ab71b 100644 --- a/packages/base-driver/test/unit/jsonwp-proxy/proxy.spec.js +++ b/packages/base-driver/test/unit/jsonwp-proxy/proxy.spec.js @@ -1,15 +1,17 @@ // transpile:mocha -import { JWProxy } from '../../../lib'; +import {JWProxy} from '../../../lib'; import request from './mock-request'; -import { isErrorType, errors } from '../../../lib/protocol/errors'; -import { getTestPort, TEST_HOST } from '../../helpers'; +import {isErrorType, errors} from '../../../lib/protocol/errors'; +import {getTestPort, TEST_HOST} from '../../helpers'; -function buildReqRes (url, method, body) { +function buildReqRes(url, method, body) { let req = {originalUrl: url, method, body}; let res = {}; res.headers = {}; - res.set = (k, v) => { res[k] = v; }; + res.set = (k, v) => { + res[k] = v; + }; res.status = (code) => { res.sentCode = code; return res; @@ -26,7 +28,7 @@ function buildReqRes (url, method, body) { describe('proxy', function () { let port; - function mockProxy (opts = {}) { + function mockProxy(opts = {}) { // sets default server/port opts = {server: TEST_HOST, port, ...opts}; let proxy = new JWProxy(opts); @@ -47,7 +49,9 @@ describe('proxy', function () { }); it('should save session id on session creation', async function () { let j = mockProxy(); - let [res, body] = await j.proxy('/session', 'POST', {desiredCapabilities: {}}); + let [res, body] = await j.proxy('/session', 'POST', { + desiredCapabilities: {}, + }); res.statusCode.should.equal(200); body.should.eql({status: 0, sessionId: '123', value: {browserName: 'boo'}}); j.sessionId.should.equal('123'); @@ -55,27 +59,32 @@ describe('proxy', function () { describe('getUrlForProxy', function () { it('should modify session id, host, and port', function () { let j = mockProxy({sessionId: '123'}); - j.getUrlForProxy('http://host.com:1234/session/456/element/200/value') - .should.eql(`http://${TEST_HOST}:${port}/session/123/element/200/value`); + j.getUrlForProxy('http://host.com:1234/session/456/element/200/value').should.eql( + `http://${TEST_HOST}:${port}/session/123/element/200/value` + ); }); it('should prepend scheme, host and port if not provided', function () { let j = mockProxy({sessionId: '123'}); - j.getUrlForProxy('/session/456/element/200/value') - .should.eql(`http://${TEST_HOST}:${port}/session/123/element/200/value`); + j.getUrlForProxy('/session/456/element/200/value').should.eql( + `http://${TEST_HOST}:${port}/session/123/element/200/value` + ); }); it('should respect nonstandard incoming request base path', function () { let j = mockProxy({sessionId: '123', reqBasePath: ''}); - j.getUrlForProxy('/session/456/element/200/value') - .should.eql(`http://${TEST_HOST}:${port}/session/123/element/200/value`); + j.getUrlForProxy('/session/456/element/200/value').should.eql( + `http://${TEST_HOST}:${port}/session/123/element/200/value` + ); j = mockProxy({sessionId: '123', reqBasePath: '/my/base/path'}); - j.getUrlForProxy('/my/base/path/session/456/element/200/value') - .should.eql(`http://${TEST_HOST}:${port}/session/123/element/200/value`); + j.getUrlForProxy('/my/base/path/session/456/element/200/value').should.eql( + `http://${TEST_HOST}:${port}/session/123/element/200/value` + ); }); it('should work with urls which do not have session ids', function () { let j = mockProxy({sessionId: '123'}); - j.getUrlForProxy('http://host.com:1234/session') - .should.eql(`http://${TEST_HOST}:${port}/session`); + j.getUrlForProxy('http://host.com:1234/session').should.eql( + `http://${TEST_HOST}:${port}/session` + ); let newUrl = j.getUrlForProxy('/session'); newUrl.should.eql(`http://${TEST_HOST}:${port}/session`); @@ -187,7 +196,7 @@ describe('proxy', function () { let j = mockProxy({sessionId: '123'}); let [req, res] = buildReqRes('/status', 'GET'); await j.proxyReqRes(req, res); - res.sentBody.should.eql({value: {'foo': 'bar'}}); + res.sentBody.should.eql({value: {foo: 'bar'}}); }); it('should proxy strange responses', async function () { let j = mockProxy({sessionId: '123'}); diff --git a/packages/base-driver/test/unit/jsonwp-proxy/url.spec.js b/packages/base-driver/test/unit/jsonwp-proxy/url.spec.js index 735c58c1a..134541ff2 100644 --- a/packages/base-driver/test/unit/jsonwp-proxy/url.spec.js +++ b/packages/base-driver/test/unit/jsonwp-proxy/url.spec.js @@ -1,7 +1,7 @@ // transpile:mocha -import { JWProxy } from '../../../lib'; -import { getTestPort, TEST_HOST, createAppiumURL } from '../../helpers'; +import {JWProxy} from '../../../lib'; +import {getTestPort, TEST_HOST, createAppiumURL} from '../../helpers'; import _ from 'lodash'; describe('JWProxy', function () { @@ -18,7 +18,7 @@ describe('JWProxy', function () { const createProxyURL = createAppiumURL(PROXY_HOST, PROXY_PORT); const PROXY_STATUS_URL = createProxyURL('', 'status'); - function createJWProxy (opts = {}) { + function createJWProxy(opts = {}) { return new JWProxy({server: TEST_HOST, port, ...opts}); } @@ -58,7 +58,9 @@ describe('JWProxy', function () { it('should error when translating session commands without session id', function () { let incomingUrl = createProxyURL('foobar', 'element'); let j = createJWProxy(); - (() => { j.getUrlForProxy(incomingUrl); }).should.throw('session id'); + (() => { + j.getUrlForProxy(incomingUrl); + }).should.throw('session id'); }); }); @@ -90,7 +92,9 @@ describe('JWProxy', function () { it('should error session commands based off /session without session id', function () { let incomingUrl = '/session/foobar/element'; let j = createJWProxy(); - (() => { j.getUrlForProxy(incomingUrl); }).should.throw('session id'); + (() => { + j.getUrlForProxy(incomingUrl); + }).should.throw('session id'); }); it('should proxy session commands based off ', function () { let incomingUrl = '/session/3d001db2-7987-42a7-975d-8d5d5304083f/timeouts/implicit_wait'; @@ -101,7 +105,9 @@ describe('JWProxy', function () { it('should proxy session commands based off /session as ""', function () { let incomingUrl = ''; let j = createJWProxy(); - (() => { j.getUrlForProxy(incomingUrl); }).should.throw('session id'); + (() => { + j.getUrlForProxy(incomingUrl); + }).should.throw('session id'); j = createJWProxy({sessionId: '123'}); let proxyUrl = j.getUrlForProxy(incomingUrl); proxyUrl.should.equal(createTestSessionURL('123')); @@ -113,13 +119,15 @@ describe('JWProxy', function () { proxyUrl.should.equal(createTestURL('barbaz', 'element')); }); it(`should proxy session commands when '/session' is in the url`, function () { - let incomingUrl = '/session/82a9b7da-faaf-4a1d-8ef3-5e4fb5812200/cookie/session-something-or-other'; + let incomingUrl = + '/session/82a9b7da-faaf-4a1d-8ef3-5e4fb5812200/cookie/session-something-or-other'; let j = createJWProxy({sessionId: 'barbaz'}); let proxyUrl = j.getUrlForProxy(incomingUrl); proxyUrl.should.equal(createTestURL('barbaz', 'cookie/session-something-or-other')); }); it(`should proxy session commands when '/session' is in the url and not base on the original url`, function () { - let incomingUrl = '/session/82a9b7da-faaf-4a1d-8ef3-5e4fb5812200/cookie/session-something-or-other'; + let incomingUrl = + '/session/82a9b7da-faaf-4a1d-8ef3-5e4fb5812200/cookie/session-something-or-other'; let j = createJWProxy({sessionId: 'barbaz'}); let proxyUrl = j.getUrlForProxy(incomingUrl); proxyUrl.should.equal(createTestURL('barbaz', 'cookie/session-something-or-other')); @@ -127,8 +135,9 @@ describe('JWProxy', function () { it('should error session commands without /session without session id', function () { let incomingUrl = '/element'; let j = createJWProxy(); - (() => { j.getUrlForProxy(incomingUrl); }).should.throw('session id'); + (() => { + j.getUrlForProxy(incomingUrl); + }).should.throw('session id'); }); }); - }); diff --git a/packages/base-driver/test/unit/jsonwp-status/status.spec.js b/packages/base-driver/test/unit/jsonwp-status/status.spec.js index 7c2a09561..e4d55e23f 100644 --- a/packages/base-driver/test/unit/jsonwp-status/status.spec.js +++ b/packages/base-driver/test/unit/jsonwp-status/status.spec.js @@ -1,7 +1,5 @@ import _ from 'lodash'; -import { statusCodes, getSummaryByCode } from '../../../lib'; - - +import {statusCodes, getSummaryByCode} from '../../../lib'; describe('jsonwp-status', function () { describe('codes', function () { diff --git a/packages/base-driver/test/unit/protocol/errors.spec.js b/packages/base-driver/test/unit/protocol/errors.spec.js index 38a04e39f..2e463d514 100644 --- a/packages/base-driver/test/unit/protocol/errors.spec.js +++ b/packages/base-driver/test/unit/protocol/errors.spec.js @@ -1,14 +1,11 @@ -import { - errors, errorFromMJSONWPStatusCode, errorFromW3CJsonCode, isErrorType -} from '../../../lib'; -import { getResponseForW3CError } from '../../../lib/protocol/errors'; +import {errors, errorFromMJSONWPStatusCode, errorFromW3CJsonCode, isErrorType} from '../../../lib'; +import {getResponseForW3CError} from '../../../lib/protocol/errors'; import _ from 'lodash'; -import { StatusCodes as HTTPStatusCodes } from 'http-status-codes'; +import {StatusCodes as HTTPStatusCodes} from 'http-status-codes'; import path from 'path'; const basename = path.basename(__filename); - // Error codes and messages have been added according to JsonWireProtocol see // https://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes let errorsList = [ @@ -16,21 +13,24 @@ let errorsList = [ errorName: 'NoSuchDriverError', errorMsg: 'A session is either terminated or not started', error: 'invalid session id', - errorCode: 6 + errorCode: 6, }, { errorName: 'ElementClickInterceptedError', - errorMsg: 'The Element Click command could not be completed because the element receiving the events is obscuring the element that was requested clicked', + errorMsg: + 'The Element Click command could not be completed because the element receiving the events is obscuring the element that was requested clicked', error: 'element click intercepted', }, { errorName: 'ElementNotInteractableError', - errorMsg: 'A command could not be completed because the element is not pointer- or keyboard interactable', + errorMsg: + 'A command could not be completed because the element is not pointer- or keyboard interactable', error: 'element not interactable', }, { errorName: 'InsecureCertificateError', - errorMsg: 'Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate', + errorMsg: + 'Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate', error: 'insecure certificate', }, { @@ -40,91 +40,95 @@ let errorsList = [ }, { errorName: 'NoSuchElementError', - errorMsg: 'An element could not be located on the page using the ' + - 'given search parameters.', + errorMsg: 'An element could not be located on the page using the ' + 'given search parameters.', error: 'no such element', - errorCode: 7 + errorCode: 7, }, { errorName: 'NoSuchFrameError', - errorMsg: 'A request to switch to a frame could not be satisfied ' + - 'because the frame could not be found.', + errorMsg: + 'A request to switch to a frame could not be satisfied ' + + 'because the frame could not be found.', error: 'no such frame', - errorCode: 8 + errorCode: 8, }, { errorName: 'UnknownCommandError', - errorMsg: 'The requested resource could not be found, or a request ' + - 'was received using an HTTP method that is not supported by ' + - 'the mapped resource.', + errorMsg: + 'The requested resource could not be found, or a request ' + + 'was received using an HTTP method that is not supported by ' + + 'the mapped resource.', error: 'unknown command', - errorCode: 9 + errorCode: 9, }, { errorName: 'StaleElementReferenceError', - errorMsg: 'An element command failed because the referenced element is ' + - 'no longer attached to the DOM.', + errorMsg: + 'An element command failed because the referenced element is ' + + 'no longer attached to the DOM.', error: 'stale element reference', - errorCode: 10 + errorCode: 10, }, { errorName: 'ElementNotVisibleError', - errorMsg: 'An element command could not be completed because the ' + - 'element is not visible on the page.', - errorCode: 11 + errorMsg: + 'An element command could not be completed because the ' + + 'element is not visible on the page.', + errorCode: 11, }, { errorName: 'InvalidElementStateError', - errorMsg: 'An element command could not be completed because the element ' + - 'is in an invalid state (e.g. attempting to click a disabled ' + - 'element).', + errorMsg: + 'An element command could not be completed because the element ' + + 'is in an invalid state (e.g. attempting to click a disabled ' + + 'element).', error: 'invalid element state', - errorCode: 12 + errorCode: 12, }, { errorName: 'UnknownError', - errorMsg: 'An unknown server-side error occurred while processing the ' + - 'command.', + errorMsg: 'An unknown server-side error occurred while processing the ' + 'command.', error: 'unknown error', - errorCode: 13 + errorCode: 13, }, { errorName: 'ElementIsNotSelectableError', - errorMsg: 'An attempt was made to select an element that cannot ' + - 'be selected.', + errorMsg: 'An attempt was made to select an element that cannot ' + 'be selected.', error: 'element not selectable', - errorCode: 15 + errorCode: 15, }, { errorName: 'JavaScriptError', errorMsg: 'An error occurred while executing user supplied JavaScript.', error: 'javascript error', - errorCode: 17 + errorCode: 17, }, { errorName: 'XPathLookupError', errorMsg: 'An error occurred while searching for an element by XPath.', - errorCode: 19 + errorCode: 19, }, { errorName: 'TimeoutError', errorMsg: 'An operation did not complete before its timeout expired.', error: 'timeout', - errorCode: 21 + errorCode: 21, }, { errorName: 'NoSuchWindowError', - errorMsg: 'A request to switch to a different window could not be ' + - 'satisfied because the window could not be found.', + errorMsg: + 'A request to switch to a different window could not be ' + + 'satisfied because the window could not be found.', error: 'no such window', - errorCode: 23 + errorCode: 23, }, { errorName: 'InvalidCookieDomainError', - errorMsg: 'An illegal attempt was made to set a cookie under a different ' + - 'domain than the current page.', + errorMsg: + 'An illegal attempt was made to set a cookie under a different ' + + 'domain than the current page.', error: 'invalid cookie domain', - errorCode: 24 + errorCode: 24, }, { errorName: 'InvalidCoordinatesError', @@ -135,59 +139,57 @@ let errorsList = [ errorName: 'UnableToSetCookieError', errorMsg: `A request to set a cookie's value could not be satisfied.`, error: 'unable to set cookie', - errorCode: 25 + errorCode: 25, }, { errorName: 'UnexpectedAlertOpenError', errorMsg: 'A modal dialog was open, blocking this operation', error: 'unexpected alert open', - errorCode: 26 + errorCode: 26, }, { errorName: 'NoAlertOpenError', - errorMsg: 'An attempt was made to operate on a modal dialog when one was ' + - 'not open.', - errorCode: 27 + errorMsg: 'An attempt was made to operate on a modal dialog when one was ' + 'not open.', + errorCode: 27, }, { errorName: 'ScriptTimeoutError', errorMsg: 'A script did not complete before its timeout expired.', error: 'script timeout', - errorCode: 28 + errorCode: 28, }, { errorName: 'InvalidElementCoordinatesError', - errorMsg: 'The coordinates provided to an interactions operation are ' + - 'invalid.', - errorCode: 29 + errorMsg: 'The coordinates provided to an interactions operation are ' + 'invalid.', + errorCode: 29, }, { errorName: 'IMENotAvailableError', errorMsg: 'IME was not available.', - errorCode: 30 + errorCode: 30, }, { errorName: 'IMEEngineActivationFailedError', errorMsg: 'An IME engine could not be started.', - errorCode: 31 + errorCode: 31, }, { errorName: 'InvalidSelectorError', errorMsg: 'Argument was an invalid selector (e.g. XPath/CSS).', error: 'invalid selector', - errorCode: 32 + errorCode: 32, }, { errorName: 'SessionNotCreatedError', errorMsg: 'A new session could not be created.', error: 'session not created', - errorCode: 33 + errorCode: 33, }, { errorName: 'MoveTargetOutOfBoundsError', errorMsg: 'Target provided for a move action is out of bounds.', error: 'move target out of bounds', - errorCode: 34 + errorCode: 34, }, { errorName: 'NoSuchAlertError', @@ -196,18 +198,20 @@ let errorsList = [ }, { errorName: 'NoSuchCookieError', - errorMsg: 'No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document', + errorMsg: + 'No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document', error: 'no such cookie', }, { errorName: 'NotYetImplementedError', errorMsg: 'Method has not yet been implemented', error: 'unknown method', - errorCode: 405 + errorCode: 405, }, { errorName: 'UnknownCommandError', - errorMsg: 'The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.', + errorMsg: + 'The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.', error: 'unknown command', }, { @@ -226,27 +230,20 @@ describe('errors', function () { for (let error of errorsList) { it(error.errorName + ' should have a JSONWP code or W3C code and message', function () { if (error.errorCode) { - new errors[error.errorName]() - .should.have.property('jsonwpCode', error.errorCode); + new errors[error.errorName]().should.have.property('jsonwpCode', error.errorCode); } else { - new errors[error.errorName]() - .should.have.property('error', error.error); + new errors[error.errorName]().should.have.property('error', error.error); } - new errors[error.errorName]() - .should.have.property('message', error.errorMsg); + new errors[error.errorName]().should.have.property('message', error.errorMsg); }); } it('BadParametersError should not have code and should have messg', function () { - new errors.BadParametersError() - .should.not.have.property('jsonwpCode'); - new errors.BadParametersError() - .should.have.property('message'); + new errors.BadParametersError().should.not.have.property('jsonwpCode'); + new errors.BadParametersError().should.have.property('message'); }); it('ProxyRequestError should have message and jsonwp', function () { - new errors.ProxyRequestError() - .should.have.property('jsonwp'); - new errors.ProxyRequestError() - .should.have.property('message'); + new errors.ProxyRequestError().should.have.property('jsonwp'); + new errors.ProxyRequestError().should.have.property('message'); }); }); describe('errorFromMJSONWPStatusCode', function () { @@ -254,28 +251,37 @@ describe('errorFromMJSONWPStatusCode', function () { if (error.errorName !== 'NotYetImplementedError') { it(error.errorCode + ' should return correct error', function () { if (error.errorCode) { - errorFromMJSONWPStatusCode(error.errorCode) - .should.have.property('jsonwpCode', error.errorCode); - errorFromMJSONWPStatusCode(error.errorCode) - .should.have.property('message', error.errorMsg); + errorFromMJSONWPStatusCode(error.errorCode).should.have.property( + 'jsonwpCode', + error.errorCode + ); + errorFromMJSONWPStatusCode(error.errorCode).should.have.property( + 'message', + error.errorMsg + ); if (!_.includes([13, 33], error.errorCode)) { - errorFromMJSONWPStatusCode(error.errorCode, 'abcd') - .should.have.property('jsonwpCode', error.errorCode); - errorFromMJSONWPStatusCode(error.errorCode, 'abcd') - .should.have.property('message', 'abcd'); + errorFromMJSONWPStatusCode(error.errorCode, 'abcd').should.have.property( + 'jsonwpCode', + error.errorCode + ); + errorFromMJSONWPStatusCode(error.errorCode, 'abcd').should.have.property( + 'message', + 'abcd' + ); } } else { - isErrorType(errorFromMJSONWPStatusCode(error.errorCode), errors.UnknownError).should.be.true; + isErrorType(errorFromMJSONWPStatusCode(error.errorCode), errors.UnknownError).should.be + .true; } }); } } it('should throw unknown error for unknown code', function () { - errorFromMJSONWPStatusCode(99) - .should.have.property('jsonwpCode', 13); - errorFromMJSONWPStatusCode(99) - .should.have.property('message', 'An unknown server-side error occurred ' + - 'while processing the command.'); + errorFromMJSONWPStatusCode(99).should.have.property('jsonwpCode', 13); + errorFromMJSONWPStatusCode(99).should.have.property( + 'message', + 'An unknown server-side error occurred ' + 'while processing the command.' + ); }); }); describe('errorFromW3CJsonCode', function () { @@ -294,7 +300,9 @@ describe('errorFromW3CJsonCode', function () { } it('should parse unknown errors', function () { isErrorType(errorFromW3CJsonCode('not a real error code'), errors.UnknownError).should.be.true; - errorFromW3CJsonCode('not a real error code').message.should.match(/An unknown server-side error occurred/); + errorFromW3CJsonCode('not a real error code').message.should.match( + /An unknown server-side error occurred/ + ); errorFromW3CJsonCode('not a real error code').error.should.equal('unknown error'); }); }); @@ -325,11 +333,11 @@ describe('w3c Status Codes', function () { // Test the errors that we don't expect to return 400 code for (let [errorName, expectedErrorCode] of non400Errors) { errors[errorName].should.exist; - (new errors[errorName]()).should.have.property('w3cStatus', expectedErrorCode); + new errors[errorName]().should.have.property('w3cStatus', expectedErrorCode); } // Test an error that we expect to return 400 code - (new errors.ElementClickInterceptedError()).should.have.property('w3cStatus', 400); + new errors.ElementClickInterceptedError().should.have.property('w3cStatus', 400); }); }); describe('.getResponseForW3CError', function () { @@ -398,53 +406,70 @@ describe('.getActualError', function () { it('should map an unknown error to UnknownError', function () { const actualError = new errors.ProxyRequestError('Error message does not matter', { value: 'Does not matter', - status: -100 + status: -100, }).getActualError(); isErrorType(actualError, errors.UnknownError).should.be.true; }); it('should parse a JSON string', function () { - const actualError = new errors.ProxyRequestError('Error message does not matter', JSON.stringify({ - value: 'Does not matter', - status: -100 - })).getActualError(); + const actualError = new errors.ProxyRequestError( + 'Error message does not matter', + JSON.stringify({ + value: 'Does not matter', + status: -100, + }) + ).getActualError(); isErrorType(actualError, errors.UnknownError).should.be.true; }); }); describe('W3C', function () { it('should map a 404 no such element error as a NoSuchElementError', function () { - const actualError = new errors.ProxyRequestError('Error message does not matter', { - value: { - error: errors.NoSuchElementError.error(), + const actualError = new errors.ProxyRequestError( + 'Error message does not matter', + { + value: { + error: errors.NoSuchElementError.error(), + }, }, - }, HTTPStatusCodes.NOT_FOUND).getActualError(); + HTTPStatusCodes.NOT_FOUND + ).getActualError(); isErrorType(actualError, errors.NoSuchElementError).should.be.true; }); it('should map a 400 StaleElementReferenceError', function () { - const actualError = new errors.ProxyRequestError('Error message does not matter', { - value: { - error: errors.StaleElementReferenceError.error(), - + const actualError = new errors.ProxyRequestError( + 'Error message does not matter', + { + value: { + error: errors.StaleElementReferenceError.error(), + }, }, - }, HTTPStatusCodes.BAD_REQUEST).getActualError(); + HTTPStatusCodes.BAD_REQUEST + ).getActualError(); isErrorType(actualError, errors.StaleElementReferenceError).should.be.true; }); it('should map an unknown error to UnknownError', function () { - const actualError = new errors.ProxyRequestError('Error message does not matter', null, { - value: { - error: 'Not a valid w3c JSON code' - + const actualError = new errors.ProxyRequestError( + 'Error message does not matter', + null, + { + value: { + error: 'Not a valid w3c JSON code', + }, }, - }, 456).getActualError(); + 456 + ).getActualError(); isErrorType(actualError, errors.UnknownError).should.be.true; }); it('should parse a JSON string', function () { - const actualError = new errors.ProxyRequestError('Error message does not matter', JSON.stringify({ - value: { - error: errors.StaleElementReferenceError.error(), - - }, - }), HTTPStatusCodes.BAD_REQUEST).getActualError(); + const actualError = new errors.ProxyRequestError( + 'Error message does not matter', + JSON.stringify({ + value: { + error: errors.StaleElementReferenceError.error(), + }, + }), + HTTPStatusCodes.BAD_REQUEST + ).getActualError(); isErrorType(actualError, errors.StaleElementReferenceError).should.be.true; }); }); diff --git a/packages/base-driver/test/unit/protocol/routes.spec.js b/packages/base-driver/test/unit/protocol/routes.spec.js index 8a6db5cf3..e85d2e9a4 100644 --- a/packages/base-driver/test/unit/protocol/routes.spec.js +++ b/packages/base-driver/test/unit/protocol/routes.spec.js @@ -1,13 +1,10 @@ // transpile:mocha -import { _ } from 'lodash'; -import { METHOD_MAP, routeToCommandName } from '../../../lib/protocol'; +import {_} from 'lodash'; +import {METHOD_MAP, routeToCommandName} from '../../../lib/protocol'; import crypto from 'crypto'; - - describe('Protocol', function () { - // TODO test against an explicit protocol rather than a hash of a previous // protocol @@ -69,11 +66,14 @@ describe('Protocol', function () { }); it('should not find command name if incorrect input data has been specified', function () { - for (let [route, method] of [['/status', 'POST'], ['/xstatus', 'GET'], ['status', 'POST']]) { + for (let [route, method] of [ + ['/status', 'POST'], + ['/xstatus', 'GET'], + ['status', 'POST'], + ]) { const cmdName = routeToCommandName(route, method); chai.should().equal(cmdName, undefined); } }); }); - }); diff --git a/packages/base-driver/test/unit/protocol/validator.spec.js b/packages/base-driver/test/unit/protocol/validator.spec.js index 85e958d91..b20f2b4e6 100644 --- a/packages/base-driver/test/unit/protocol/validator.spec.js +++ b/packages/base-driver/test/unit/protocol/validator.spec.js @@ -1,92 +1,145 @@ // transpile:mocha -import { validators } from '../../../lib/protocol/validators'; - - +import {validators} from '../../../lib/protocol/validators'; describe('Protocol', function () { describe('direct to driver', function () { - describe('setUrl', function () { it('should fail when no url passed', function () { - (() => {validators.setUrl();}).should.throw(/url/i); + (() => { + validators.setUrl(); + }).should.throw(/url/i); }); it('should fail when given invalid url', function () { - (() => {validators.setUrl('foo');}).should.throw(/url/i); + (() => { + validators.setUrl('foo'); + }).should.throw(/url/i); }); it('should succeed when given url starting with http', function () { - (() => {validators.setUrl('http://appium.io');}).should.not.throw(); + (() => { + validators.setUrl('http://appium.io'); + }).should.not.throw(); }); it('should succeed when given an android-like scheme', function () { - (() => {validators.setUrl('content://contacts/people/1');}).should.not.throw(); + (() => { + validators.setUrl('content://contacts/people/1'); + }).should.not.throw(); }); it('should succeed with hyphens dots and plus chars in the scheme', function () { - (() => {validators.setUrl('my-app.a+b://login');}).should.not.throw(); + (() => { + validators.setUrl('my-app.a+b://login'); + }).should.not.throw(); }); it('should succeed when given an about scheme', function () { - (() => {validators.setUrl('about:blank');}).should.not.throw(); + (() => { + validators.setUrl('about:blank'); + }).should.not.throw(); }); it('should succeed when given a data scheme', function () { - (() => {validators.setUrl('data:text/html,');}).should.not.throw(); + (() => { + validators.setUrl('data:text/html,'); + }).should.not.throw(); }); }); describe('implicitWait', function () { it('should fail when given no ms', function () { - (() => {validators.implicitWait();}).should.throw(/ms/i); + (() => { + validators.implicitWait(); + }).should.throw(/ms/i); }); it('should fail when given a non-numeric ms', function () { - (() => {validators.implicitWait('five');}).should.throw(/ms/i); + (() => { + validators.implicitWait('five'); + }).should.throw(/ms/i); }); it('should fail when given a negative ms', function () { - (() => {validators.implicitWait(-1);}).should.throw(/ms/i); + (() => { + validators.implicitWait(-1); + }).should.throw(/ms/i); }); it('should succeed when given an ms of 0', function () { - (() => {validators.implicitWait(0);}).should.not.throw(); + (() => { + validators.implicitWait(0); + }).should.not.throw(); }); it('should succeed when given an ms greater than 0', function () { - (() => {validators.implicitWait(100);}).should.not.throw(); + (() => { + validators.implicitWait(100); + }).should.not.throw(); }); }); describe('asyncScriptTimeout', function () { it('should fail when given no ms', function () { - (() => {validators.asyncScriptTimeout();}).should.throw(/ms/i); + (() => { + validators.asyncScriptTimeout(); + }).should.throw(/ms/i); }); it('should fail when given a non-numeric ms', function () { - (() => {validators.asyncScriptTimeout('five');}).should.throw(/ms/i); + (() => { + validators.asyncScriptTimeout('five'); + }).should.throw(/ms/i); }); it('should fail when given a negative ms', function () { - (() => {validators.asyncScriptTimeout(-1);}).should.throw(/ms/i); + (() => { + validators.asyncScriptTimeout(-1); + }).should.throw(/ms/i); }); it('should succeed when given an ms of 0', function () { - (() => {validators.asyncScriptTimeout(0);}).should.not.throw(); + (() => { + validators.asyncScriptTimeout(0); + }).should.not.throw(); }); it('should succeed when given an ms greater than 0', function () { - (() => {validators.asyncScriptTimeout(100);}).should.not.throw(); + (() => { + validators.asyncScriptTimeout(100); + }).should.not.throw(); }); }); describe('clickCurrent', function () { it('should fail when given an invalid button', function () { - (() => {validators.clickCurrent(4);}).should.throw(/0, 1, or 2/i); + (() => { + validators.clickCurrent(4); + }).should.throw(/0, 1, or 2/i); }); it('should succeed when given a valid button', function () { - (() => {validators.clickCurrent(0);}).should.not.throw(); - (() => {validators.clickCurrent(1);}).should.not.throw(); - (() => {validators.clickCurrent(2);}).should.not.throw(); + (() => { + validators.clickCurrent(0); + }).should.not.throw(); + (() => { + validators.clickCurrent(1); + }).should.not.throw(); + (() => { + validators.clickCurrent(2); + }).should.not.throw(); }); }); describe('setNetworkConnection', function () { it('should fail when given no type', function () { - (() => {validators.setNetworkConnection();}).should.throw(/0, 1, 2, 4, 6/i); + (() => { + validators.setNetworkConnection(); + }).should.throw(/0, 1, 2, 4, 6/i); }); it('should fail when given an invalid type', function () { - (() => {validators.setNetworkConnection(8);}).should.throw(/0, 1, 2, 4, 6/i); + (() => { + validators.setNetworkConnection(8); + }).should.throw(/0, 1, 2, 4, 6/i); }); it('should succeed when given a valid type', function () { - (() => {validators.setNetworkConnection(0);}).should.not.throw(); - (() => {validators.setNetworkConnection(1);}).should.not.throw(); - (() => {validators.setNetworkConnection(2);}).should.not.throw(); - (() => {validators.setNetworkConnection(4);}).should.not.throw(); - (() => {validators.setNetworkConnection(6);}).should.not.throw(); + (() => { + validators.setNetworkConnection(0); + }).should.not.throw(); + (() => { + validators.setNetworkConnection(1); + }).should.not.throw(); + (() => { + validators.setNetworkConnection(2); + }).should.not.throw(); + (() => { + validators.setNetworkConnection(4); + }).should.not.throw(); + (() => { + validators.setNetworkConnection(6); + }).should.not.throw(); }); }); }); diff --git a/packages/base-plugin/lib/plugin.js b/packages/base-plugin/lib/plugin.js index 25cd2f94e..e40b23e10 100644 --- a/packages/base-plugin/lib/plugin.js +++ b/packages/base-plugin/lib/plugin.js @@ -1,8 +1,6 @@ -import { logger } from '@appium/support'; - +import {logger} from '@appium/support'; export default class BasePlugin { - // plugins can define new methods for the Appium server to map to command names, of the same // format as used in Appium's routes.js, for example, this would be a valid newMethodMap: // @@ -14,7 +12,7 @@ export default class BasePlugin { // } static newMethodMap = {}; - constructor (pluginName, opts = {}) { + constructor(pluginName, opts = {}) { this.name = pluginName; this.logger = logger.getLogger(`Plugin [${pluginName}]`); @@ -22,7 +20,6 @@ export default class BasePlugin { this.opts = opts; } - /** * Optionally updates an Appium express app and http server, by calling methods that may mutate * those objects. For example, you could call: @@ -68,5 +65,4 @@ export default class BasePlugin { /*async handle (next, driver, cmdName, ...args) { return await next(); }*/ - } diff --git a/packages/base-plugin/test/helpers.js b/packages/base-plugin/test/helpers.js index c8565ceb7..47c57105b 100644 --- a/packages/base-plugin/test/helpers.js +++ b/packages/base-plugin/test/helpers.js @@ -1,12 +1,11 @@ - // @ts-check /* eslint-disable no-console */ -import { fs } from '@appium/support'; -import { main as appiumServer } from 'appium'; +import {fs} from '@appium/support'; +import {main as appiumServer} from 'appium'; import getPort from 'get-port'; -import { info, success, warning } from 'log-symbols'; -import { exec } from 'teen_process'; +import {info, success, warning} from 'log-symbols'; +import {exec} from 'teen_process'; const APPIUM_BIN = require.resolve('appium'); @@ -15,7 +14,7 @@ const APPIUM_BIN = require.resolve('appium'); * @param {E2ESetupOpts} opts * @returns {void} */ -function e2eSetup (opts) { +function e2eSetup(opts) { let { appiumHome, before, @@ -61,36 +60,19 @@ function e2eSetup (opts) { const installDriver = async (env) => { console.log(`${info} Checking if driver "${driverName}" is installed...`); const driverListArgs = [APPIUM_BIN, 'driver', 'list', '--json']; - console.log( - `${info} Running: ${process.execPath} ${driverListArgs.join(' ')}`, - ); - const {stdout: driverListJson} = await exec( - process.execPath, - driverListArgs, - { - env, - }, - ); + console.log(`${info} Running: ${process.execPath} ${driverListArgs.join(' ')}`); + const {stdout: driverListJson} = await exec(process.execPath, driverListArgs, { + env, + }); const installedDrivers = JSON.parse(driverListJson); if (!installedDrivers[driverName]?.installed) { - console.log( - `${warning} Driver "${driverName}" not installed; installing...`, - ); - const driverArgs = [ - APPIUM_BIN, - 'driver', - 'install', - '--source', - driverSource, - driverSpec, - ]; + console.log(`${warning} Driver "${driverName}" not installed; installing...`); + const driverArgs = [APPIUM_BIN, 'driver', 'install', '--source', driverSource, driverSpec]; if (driverPackage) { driverArgs.push('--package', driverPackage); } - console.log( - `${info} Running: ${process.execPath} ${driverArgs.join(' ')}`, - ); + console.log(`${info} Running: ${process.execPath} ${driverArgs.join(' ')}`); await exec(process.execPath, driverArgs, { env, }); @@ -105,33 +87,18 @@ function e2eSetup (opts) { const installPlugin = async (env) => { console.log(`${info} Checking if plugin "${pluginName}" is installed...`); const pluginListArgs = [APPIUM_BIN, 'plugin', 'list', '--json']; - const {stdout: pluginListJson} = await exec( - process.execPath, - pluginListArgs, - { - env, - }, - ); + const {stdout: pluginListJson} = await exec(process.execPath, pluginListArgs, { + env, + }); const installedPlugins = JSON.parse(pluginListJson); if (!installedPlugins[pluginName]?.installed) { - console.log( - `${warning} Plugin "${pluginName}" not installed; installing...`, - ); - const pluginArgs = [ - APPIUM_BIN, - 'plugin', - 'install', - '--source', - pluginSource, - pluginSpec, - ]; + console.log(`${warning} Plugin "${pluginName}" not installed; installing...`); + const pluginArgs = [APPIUM_BIN, 'plugin', 'install', '--source', pluginSource, pluginSpec]; if (pluginPackage) { pluginArgs.push('--package', pluginPackage); } - console.log( - `${info} Running: ${process.execPath} ${pluginArgs.join(' ')}`, - ); + console.log(`${info} Running: ${process.execPath} ${pluginArgs.join(' ')}`); await exec(process.execPath, pluginArgs, { env, }); @@ -153,9 +120,9 @@ function e2eSetup (opts) { usePlugins: [pluginName], useDrivers: [driverName], appiumHome, - ...serverArgs + ...serverArgs, }; - server = /** @type {AppiumServer} */(await appiumServer(args)); + server = /** @type {AppiumServer} */ (await appiumServer(args)); }; const env = await setupAppiumHome(); @@ -171,7 +138,7 @@ function e2eSetup (opts) { }); } -export { e2eSetup }; +export {e2eSetup}; /** * @typedef E2ESetupOpts @@ -191,7 +158,6 @@ export { e2eSetup }; * @property {string} [host] - Host to use for Appium server */ - /** * @typedef {import('@appium/types').AppiumServer} AppiumServer */ diff --git a/packages/doctor/bin/appium-doctor.js b/packages/doctor/bin/appium-doctor.js index d0d13be66..447726669 100755 --- a/packages/doctor/bin/appium-doctor.js +++ b/packages/doctor/bin/appium-doctor.js @@ -2,10 +2,9 @@ import yargs from 'yargs'; import newDoctor from '../lib/factory'; -import { configureBinaryLog } from '../lib/utils'; -import { configure as configurePrompt } from '../lib/prompt'; -import { system } from '@appium/support'; - +import {configureBinaryLog} from '../lib/utils'; +import {configure as configurePrompt} from '../lib/prompt'; +import {system} from '@appium/support'; yargs .strict() @@ -35,13 +34,18 @@ yargs }); // make sure we use the general checks for every test -let opts = Object.assign({ - general: true, -}, yargs.argv); +let opts = Object.assign( + { + general: true, + }, + yargs.argv +); configurePrompt(opts); configureBinaryLog(opts); -newDoctor(opts).run().catch(function (e) { - console.error(e); // eslint-disable-line no-console - process.exit(1); -}); +newDoctor(opts) + .run() + .catch(function (e) { + console.error(e); // eslint-disable-line no-console + process.exit(1); + }); diff --git a/packages/doctor/lib/android.js b/packages/doctor/lib/android.js index 370789145..3a69d4aba 100644 --- a/packages/doctor/lib/android.js +++ b/packages/doctor/lib/android.js @@ -1,10 +1,10 @@ -import { DoctorCheck } from './doctor'; -import { ok, nok, okOptional, nokOptional, resolveExecutablePath } from './utils'; -import { system, fs } from '@appium/support'; +import {DoctorCheck} from './doctor'; +import {ok, nok, okOptional, nokOptional, resolveExecutablePath} from './utils'; +import {system, fs} from '@appium/support'; import path from 'path'; import EnvVarAndPathCheck from './env'; import '@colors/colors'; -import { getAndroidBinaryPath, getSdkRootFromEnv } from 'appium-adb'; +import {getAndroidBinaryPath, getSdkRootFromEnv} from 'appium-adb'; import log from './logger'; let checks = []; @@ -16,35 +16,41 @@ checks.push(new EnvVarAndPathCheck('JAVA_HOME')); // Check that the PATH includes the jdk's bin directory class JavaOnPathCheck extends DoctorCheck { - async diagnose () { + async diagnose() { if (process.env.JAVA_HOME) { const javaHomeBin = path.resolve(process.env.JAVA_HOME, 'bin'); - return await fs.exists(javaHomeBin) + return (await fs.exists(javaHomeBin)) ? ok(`'bin' subfolder exists under '${process.env.JAVA_HOME}'`) - : nok(`'bin' subfolder does not exist under '${process.env.JAVA_HOME}'. ` + - `Is ${javaHome} set to a proper value?`); + : nok( + `'bin' subfolder does not exist under '${process.env.JAVA_HOME}'. ` + + `Is ${javaHome} set to a proper value?` + ); } - return nok(`Cannot check ${javaHome} requirements since the environment variable itself is not set`); + return nok( + `Cannot check ${javaHome} requirements since the environment variable itself is not set` + ); } - fix () { + fix() { return `Set ${javaHome} environment variable to the root folder path of your local JDK installation`; } } // Check tools class AndroidToolCheck extends DoctorCheck { - constructor () { + constructor() { super(); this.tools = ['adb', 'android', 'emulator', `apkanalyzer${system.isWindows() ? '.bat' : ''}`]; this.noBinaries = []; } - async diagnose () { + async diagnose() { const listOfTools = this.tools.join(', '); const sdkRoot = getSdkRootFromEnv(); if (!sdkRoot) { - return nok(`${listOfTools} could not be found because ANDROID_HOME or ANDROID_SDK_ROOT is NOT set!`); + return nok( + `${listOfTools} could not be found because ANDROID_HOME or ANDROID_SDK_ROOT is NOT set!` + ); } log.info(` Checking ${listOfTools}`); @@ -64,30 +70,40 @@ class AndroidToolCheck extends DoctorCheck { return ok(`${listOfTools} exist: ${sdkRoot}`); } - fix () { + fix() { if (typeof process.env.ANDROID_HOME === 'undefined') { return `Manually configure ${'ANDROID_HOME'.bold} and run appium-doctor again.`; } - return `Manually install ${this.noBinaries.join(', ').bold} and add it to ${'PATH'.bold}. ` + + return ( + `Manually install ${this.noBinaries.join(', ').bold} and add it to ${'PATH'.bold}. ` + 'https://developer.android.com/studio#cmdline-tools and ' + - 'https://developer.android.com/studio/intro/update#sdk-manager may help to setup.'; + 'https://developer.android.com/studio/intro/update#sdk-manager may help to setup.' + ); } } checks.push(new AndroidToolCheck()); checks.push(new JavaOnPathCheck()); class OptionalAppBundleCheck extends DoctorCheck { - async diagnose () { + async diagnose() { const bundletoolPath = await resolveExecutablePath('bundletool.jar'); return bundletoolPath ? okOptional(`bundletool.jar is installed at: ${bundletoolPath}`) : nokOptional('bundletool.jar cannot be found'); } - - async fix () { // eslint-disable-line require-await - return `${'bundletool.jar'.bold} is used to handle Android App Bundle. Please read http://appium.io/docs/en/writing-running-appium/android/android-appbundle/ to install it` + - `${system.isWindows() ? '. Also consider adding the ".jar" extension into your PATHEXT environment variable in order to fix the problem for Windows' : ''}`; + // eslint-disable-next-line require-await + async fix() { + return ( + `${ + 'bundletool.jar'.bold + } is used to handle Android App Bundle. Please read http://appium.io/docs/en/writing-running-appium/android/android-appbundle/ to install it` + + `${ + system.isWindows() + ? '. Also consider adding the ".jar" extension into your PATHEXT environment variable in order to fix the problem for Windows' + : '' + }` + ); } } checks.push(new OptionalAppBundleCheck()); @@ -96,21 +112,33 @@ class OptionalGstreamerCheck extends DoctorCheck { GSTREAMER_BINARY = `gst-launch-1.0${system.isWindows() ? '.exe' : ''}`; GST_INSPECT_BINARY = `gst-inspect-1.0${system.isWindows() ? '.exe' : ''}`; - async diagnose () { + async diagnose() { const gstreamerPath = await resolveExecutablePath(this.GSTREAMER_BINARY); const gstInspectPath = await resolveExecutablePath(this.GST_INSPECT_BINARY); return gstreamerPath && gstInspectPath - ? okOptional(`${this.GSTREAMER_BINARY} and ${this.GST_INSPECT_BINARY} are installed at: ${gstreamerPath} and ${gstInspectPath}`) + ? okOptional( + `${this.GSTREAMER_BINARY} and ${this.GST_INSPECT_BINARY} are installed at: ${gstreamerPath} and ${gstInspectPath}` + ) : nokOptional(`${this.GSTREAMER_BINARY} and/or ${this.GST_INSPECT_BINARY} cannot be found`); } - - async fix () { // eslint-disable-line require-await - return `${`${this.GSTREAMER_BINARY} and ${this.GST_INSPECT_BINARY}`.bold} are used to stream the screen of the device under test. ` + - 'Please read https://appium.io/docs/en/writing-running-appium/android/android-screen-streaming/ to install them and for more details'; + // eslint-disable-next-line require-await + async fix() { + return ( + `${ + `${this.GSTREAMER_BINARY} and ${this.GST_INSPECT_BINARY}`.bold + } are used to stream the screen of the device under test. ` + + 'Please read https://appium.io/docs/en/writing-running-appium/android/android-screen-streaming/ to install them and for more details' + ); } } checks.push(new OptionalGstreamerCheck()); -export { EnvVarAndPathCheck, AndroidToolCheck, JavaOnPathCheck, OptionalAppBundleCheck, OptionalGstreamerCheck }; +export { + EnvVarAndPathCheck, + AndroidToolCheck, + JavaOnPathCheck, + OptionalAppBundleCheck, + OptionalGstreamerCheck, +}; export default checks; diff --git a/packages/doctor/lib/demo.js b/packages/doctor/lib/demo.js index 0635e1cb7..c11256368 100644 --- a/packages/doctor/lib/demo.js +++ b/packages/doctor/lib/demo.js @@ -1,31 +1,31 @@ // demo rule to test the gui -import { ok, nok } from './utils'; -import { fs } from '@appium/support'; -import { exec } from 'teen_process'; -import { DoctorCheck, FixSkippedError } from './doctor'; +import {ok, nok} from './utils'; +import {fs} from '@appium/support'; +import {exec} from 'teen_process'; +import {DoctorCheck, FixSkippedError} from './doctor'; import log from './logger'; -import { fixIt } from './prompt'; - +import {fixIt} from './prompt'; let checks = []; class DirCheck extends DoctorCheck { - constructor (path) { + constructor(path) { super({autofix: false}); this.path = path; } - async diagnose () { - if (!await fs.exists(this.path)) { + async diagnose() { + if (!(await fs.exists(this.path))) { return nok(`Could NOT find directory at '${this.path}'!`); } let stats = await fs.lstat(this.path); - return stats.isDirectory() ? - ok(`Found directory at: ${this.path}`) : nok(`'${this.path}' is NOT a directory!`); + return stats.isDirectory() + ? ok(`Found directory at: ${this.path}`) + : nok(`'${this.path}' is NOT a directory!`); } - - async fix () { // eslint-disable-line require-await + // eslint-disable-next-line require-await + async fix() { return `Manually create a directory at: ${this.path}`; } } @@ -34,17 +34,18 @@ checks.push(new DirCheck('/tmp/appium-doctor')); checks.push(new DirCheck('/tmp/appium-doctor/demo')); class FileCheck extends DoctorCheck { - constructor (path) { + constructor(path) { super({autofix: true}); this.path = path; } - async diagnose () { - return await fs.exists(this.path) ? - ok(`Found file at: ${this.path}`) : nok(`Could NOT find file at '${this.path}'!`); + async diagnose() { + return (await fs.exists(this.path)) + ? ok(`Found file at: ${this.path}`) + : nok(`Could NOT find file at '${this.path}'!`); } - async fix () { + async fix() { log.info(`The following command need be executed: touch '${this.path}'`); let yesno = await fixIt(); if (yesno === 'yes') { @@ -60,5 +61,5 @@ checks.push(new FileCheck('/tmp/appium-doctor/demo/apple.fruit')); checks.push(new FileCheck('/tmp/appium-doctor/demo/pear.fruit')); checks.push(new FileCheck('/tmp/appium-doctor/demo/orange.fruit')); -export { DirCheck, FileCheck }; +export {DirCheck, FileCheck}; export default checks; diff --git a/packages/doctor/lib/dev.js b/packages/doctor/lib/dev.js index 450aef03f..099ac285c 100644 --- a/packages/doctor/lib/dev.js +++ b/packages/doctor/lib/dev.js @@ -1,6 +1,6 @@ -import { DoctorCheck } from './doctor'; -import { ok, nok, resolveExecutablePath } from './utils'; -import { fs, system } from '@appium/support'; +import {DoctorCheck} from './doctor'; +import {ok, nok, resolveExecutablePath} from './utils'; +import {fs, system} from '@appium/support'; import path from 'path'; import '@colors/colors'; @@ -8,12 +8,12 @@ let checks = []; // Check PATH binaries class BinaryIsInPathCheck extends DoctorCheck { - constructor (binary) { + constructor(binary) { super(); this.binary = binary; } - async diagnose () { + async diagnose() { const resolvedPath = await resolveExecutablePath(this.binary); if (!resolvedPath) { return nok(`${this.binary} is MISSING in PATH: ${process.env.PATH}`); @@ -22,7 +22,7 @@ class BinaryIsInPathCheck extends DoctorCheck { return ok(`${this.binary} was found at ${resolvedPath}`); } - fix () { + fix() { return `Manually install the ${this.binary.bold} binary and add it to ${'PATH'.bold}.`; } } @@ -33,21 +33,22 @@ checks.push(new BinaryIsInPathCheck(system.isWindows() ? 'adb.exe' : 'adb')); // Check Android SDKs class AndroidSdkExists extends DoctorCheck { - constructor (sdk) { + constructor(sdk) { super(); this.sdk = sdk; } - async diagnose () { + async diagnose() { if (typeof process.env.ANDROID_HOME === 'undefined') { return nok(`${this.sdk} could not be found because ANDROID_HOME is NOT set!`); } let sdkPath = path.resolve(process.env.ANDROID_HOME, path.join('platforms', this.sdk)); - return await fs.exists(sdkPath) ? ok(`${this.sdk} was found at: ${sdkPath}`) : - nok(`${this.sdk} could NOT be found at '${sdkPath}'!`); + return (await fs.exists(sdkPath)) + ? ok(`${this.sdk} was found at: ${sdkPath}`) + : nok(`${this.sdk} could NOT be found at '${sdkPath}'!`); } - fix () { + fix() { if (typeof process.env.ANDROID_HOME === 'undefined') { return `Manually configure ${'ANDROID_HOME'.bold}.`; } @@ -58,5 +59,5 @@ class AndroidSdkExists extends DoctorCheck { checks.push(new AndroidSdkExists('android-16')); checks.push(new AndroidSdkExists('android-19')); -export { BinaryIsInPathCheck, AndroidSdkExists }; +export {BinaryIsInPathCheck, AndroidSdkExists}; export default checks; diff --git a/packages/doctor/lib/doctor.js b/packages/doctor/lib/doctor.js index 3cf36d836..f5e901a3e 100644 --- a/packages/doctor/lib/doctor.js +++ b/packages/doctor/lib/doctor.js @@ -1,40 +1,41 @@ import '@colors/colors'; import _ from 'lodash'; import log from './logger'; -import { fs } from '@appium/support'; +import {fs} from '@appium/support'; const {version} = fs.readPackageJsonFrom(__dirname); -class FixSkippedError extends Error { -} +class FixSkippedError extends Error {} class DoctorCheck { - constructor (opts = {}) { + constructor(opts = {}) { this.autofix = !!opts.autofix; } - diagnose () { throw new Error('Not Implemented!'); } + diagnose() { + throw new Error('Not Implemented!'); + } - fix () { + fix() { // return string for manual fixes. throw new Error('Not Implemented!'); } } class Doctor { - constructor () { + constructor() { this.checks = []; this.checkOptionals = []; this.toFix = []; this.toFixOptionals = []; } - register (checks) { + register(checks) { checks = Array.isArray(checks) ? checks : [checks]; this.checks = this.checks.concat(checks); } - async diagnose () { + async diagnose() { log.info(`### Diagnostic for ${'necessary'.green} dependencies starting ###`); this.toFix = []; for (const check of this.checks) { @@ -45,19 +46,32 @@ class Doctor { } await this.diagnosticResultMessage(res, this.toFix, check); } - log.info(`### Diagnostic for necessary dependencies completed, ${await this.fixMessage(this.toFix.length)}. ###`); + log.info( + `### Diagnostic for necessary dependencies completed, ${await this.fixMessage( + this.toFix.length + )}. ###` + ); log.info(''); log.info(`### Diagnostic for ${'optional'.yellow} dependencies starting ###`); this.toFixOptionals = []; for (const checkOptional of this.checkOptionals) { - await this.diagnosticResultMessage(await checkOptional.diagnose(), this.toFixOptionals, checkOptional); + await this.diagnosticResultMessage( + await checkOptional.diagnose(), + this.toFixOptionals, + checkOptional + ); } - log.info(`### Diagnostic for optional dependencies completed, ${await this.fixMessage(this.toFixOptionals.length, true)}. ###`); + log.info( + `### Diagnostic for optional dependencies completed, ${await this.fixMessage( + this.toFixOptionals.length, + true + )}. ###` + ); log.info(''); } - async reportManualFixes (fix, fixOptioal) { + async reportManualFixes(fix, fixOptioal) { const manualFixes = _.filter(fix, (f) => !f?.check?.autofix); const manualFixesOptional = _.filter(fixOptioal, (f) => !f?.check?.autofix); @@ -98,7 +112,7 @@ class Doctor { return false; } - async runAutoFix (f) { + async runAutoFix(f) { log.info(`### Fixing: ${f.error} ###`); try { await f.check.fix(); @@ -124,7 +138,7 @@ class Doctor { } } - async runAutoFixes () { + async runAutoFixes() { let autoFixes = _.filter(this.toFix, (f) => f?.check?.autofix); for (let f of autoFixes) { await this.runAutoFix(f); @@ -140,7 +154,7 @@ class Doctor { log.info(''); } - async run () { + async run() { log.info(`Appium Doctor v.${version}`); await this.diagnose(); if (await this.reportSuccess(this.toFix.length, this.toFixOptionals.length)) { @@ -153,20 +167,24 @@ class Doctor { } //// generating messages - async diagnosticResultMessage (result, toFixList, check) { // eslint-disable-line require-await + // eslint-disable-next-line require-await + async diagnosticResultMessage(result, toFixList, check) { if (result.ok) { log.info(` ${'\u2714'.green} ${result.message}`); } else { - const errorMessage = result.optional ? ` ${'\u2716'.yellow} ${result.message}` : ` ${'\u2716'.red} ${result.message}`; + const errorMessage = result.optional + ? ` ${'\u2716'.yellow} ${result.message}` + : ` ${'\u2716'.red} ${result.message}`; log.warn(errorMessage); toFixList.push({ error: errorMessage, - check + check, }); } } - async fixMessage (length, optional = false) { // eslint-disable-line require-await + // eslint-disable-next-line require-await + async fixMessage(length, optional = false) { let message; switch (length) { case 0: @@ -180,8 +198,8 @@ class Doctor { } return `${message} ${optional ? 'possible' : 'needed'}`; } - - async reportSuccess (length, lengthOptional) { // eslint-disable-line require-await + // eslint-disable-next-line require-await + async reportSuccess(length, lengthOptional) { if (length === 0 && lengthOptional === 0) { log.info('Everything looks good, bye!'); log.info(''); @@ -192,4 +210,4 @@ class Doctor { } } -export { Doctor, DoctorCheck, FixSkippedError }; +export {Doctor, DoctorCheck, FixSkippedError}; diff --git a/packages/doctor/lib/env.js b/packages/doctor/lib/env.js index 51720d0de..773464de6 100644 --- a/packages/doctor/lib/env.js +++ b/packages/doctor/lib/env.js @@ -1,29 +1,31 @@ -import { fs } from '@appium/support'; -import { DoctorCheck } from './doctor'; -import { ok, nok } from './utils'; +import {fs} from '@appium/support'; +import {DoctorCheck} from './doctor'; +import {ok, nok} from './utils'; import '@colors/colors'; // Check env variables class EnvVarAndPathCheck extends DoctorCheck { - constructor (varName) { + constructor(varName) { super(); this.varName = varName; } - async diagnose () { + async diagnose() { let varValue = process.env[this.varName]; if (typeof varValue === 'undefined') { return nok(`${this.varName} environment variable is NOT set!`); } - return await fs.exists(varValue) ? ok(`${this.varName} is set to: ${varValue}`) : - nok(`${this.varName} is set to '${varValue}' but this is NOT a valid path!`); + return (await fs.exists(varValue)) + ? ok(`${this.varName} is set to: ${varValue}`) + : nok(`${this.varName} is set to '${varValue}' but this is NOT a valid path!`); } - fix () { - return `Make sure the environment variable ${this.varName.bold} is properly configured for the Appium process. ` + - `Refer https://github.com/appium/java-client/blob/master/docs/environment.md for more details.`; + fix() { + return ( + `Make sure the environment variable ${this.varName.bold} is properly configured for the Appium process. ` + + `Refer https://github.com/appium/java-client/blob/master/docs/environment.md for more details.` + ); } } - export default EnvVarAndPathCheck; diff --git a/packages/doctor/lib/factory.js b/packages/doctor/lib/factory.js index 608f4a4f2..b5cba23bb 100644 --- a/packages/doctor/lib/factory.js +++ b/packages/doctor/lib/factory.js @@ -1,12 +1,11 @@ import _ from 'lodash'; -import { Doctor } from './doctor'; +import {Doctor} from './doctor'; import generalChecks from './general'; import iosChecks from './ios'; import androidChecks from './android'; import devChecks from './dev'; import demoChecks from './demo'; - let checks = {generalChecks, iosChecks, androidChecks, devChecks, demoChecks}; let newDoctor = (opts) => { diff --git a/packages/doctor/lib/general.js b/packages/doctor/lib/general.js index 6cdb6e449..e0270a3a5 100644 --- a/packages/doctor/lib/general.js +++ b/packages/doctor/lib/general.js @@ -1,22 +1,23 @@ -import { ok, nok, okOptional, nokOptional, resolveExecutablePath, getNpmPackageInfo } from './utils'; -import { exec } from 'teen_process'; -import { DoctorCheck } from './doctor'; +import {ok, nok, okOptional, nokOptional, resolveExecutablePath, getNpmPackageInfo} from './utils'; +import {exec} from 'teen_process'; +import {DoctorCheck} from './doctor'; import NodeDetector from './node-detector'; -import { util } from '@appium/support'; -import { EOL } from 'os'; +import {util} from '@appium/support'; +import {EOL} from 'os'; import '@colors/colors'; let checks = []; // Node Binary class NodeBinaryCheck extends DoctorCheck { - async diagnose () { + async diagnose() { let nodePath = await NodeDetector.detect(); - return nodePath ? ok(`The Node.js binary was found at: ${nodePath}`) : - nok('The Node.js binary was NOT found!'); + return nodePath + ? ok(`The Node.js binary was found at: ${nodePath}`) + : nok('The Node.js binary was NOT found!'); } - fix () { + fix() { return `Manually setup ${'Node.js'.bold}.`; } } @@ -26,7 +27,7 @@ const REQUIRED_NODE_VERSION = '10.0.0'; // Node version class NodeVersionCheck extends DoctorCheck { - async diagnose () { + async diagnose() { let nodePath = await NodeDetector.detect(); if (!nodePath) { return nok('Node is not installed, so no version to check!'); @@ -42,45 +43,57 @@ class NodeVersionCheck extends DoctorCheck { } } - fix () { + fix() { return `Manually upgrade ${'Node.js'.bold}.`; } } checks.push(new NodeVersionCheck()); class OptionalFfmpegCommandCheck extends DoctorCheck { - async diagnose () { + async diagnose() { const ffmpegPath = await resolveExecutablePath('ffmpeg'); return ffmpegPath - ? okOptional(`ffmpeg is installed at: ${ffmpegPath}. ${(await exec('ffmpeg', ['-version'])).stdout.split(EOL)[0]}`) + ? okOptional( + `ffmpeg is installed at: ${ffmpegPath}. ${ + (await exec('ffmpeg', ['-version'])).stdout.split(EOL)[0] + }` + ) : nokOptional('ffmpeg cannot be found'); } - - async fix () { // eslint-disable-line require-await - return `${'ffmpeg'.bold} is needed to record screen features. Please read https://www.ffmpeg.org/ to install it`; + // eslint-disable-next-line require-await + async fix() { + return `${ + 'ffmpeg'.bold + } is needed to record screen features. Please read https://www.ffmpeg.org/ to install it`; } } checks.push(new OptionalFfmpegCommandCheck()); - class OptionalMjpegConsumerCommandCheck extends DoctorCheck { - async diagnose () { + async diagnose() { const packageName = 'mjpeg-consumer'; const packageInfo = await getNpmPackageInfo(packageName); if (packageInfo) { - return okOptional(`${packageName} is installed at: ${packageInfo.path}. Installed version is: ${packageInfo.version}`); + return okOptional( + `${packageName} is installed at: ${packageInfo.path}. Installed version is: ${packageInfo.version}` + ); } return nokOptional(`${packageName} cannot be found.`); } - - async fix () { // eslint-disable-line require-await - return `${'mjpeg-consumer'.bold} module is required to use MJPEG-over-HTTP features. Please install it with 'npm i -g mjpeg-consumer'.`; + // eslint-disable-next-line require-await + async fix() { + return `${ + 'mjpeg-consumer'.bold + } module is required to use MJPEG-over-HTTP features. Please install it with 'npm i -g mjpeg-consumer'.`; } } checks.push(new OptionalMjpegConsumerCommandCheck()); - -export { NodeBinaryCheck, NodeVersionCheck, OptionalFfmpegCommandCheck, - OptionalMjpegConsumerCommandCheck }; +export { + NodeBinaryCheck, + NodeVersionCheck, + OptionalFfmpegCommandCheck, + OptionalMjpegConsumerCommandCheck, +}; export default checks; diff --git a/packages/doctor/lib/index.js b/packages/doctor/lib/index.js index bd103b56e..5fd4f6632 100644 --- a/packages/doctor/lib/index.js +++ b/packages/doctor/lib/index.js @@ -1,4 +1,4 @@ import newDoctor from './factory'; -import { Doctor, DoctorCheck } from './doctor'; +import {Doctor, DoctorCheck} from './doctor'; -export { newDoctor, Doctor, DoctorCheck }; +export {newDoctor, Doctor, DoctorCheck}; diff --git a/packages/doctor/lib/ios.js b/packages/doctor/lib/ios.js index 627548942..68dfff991 100644 --- a/packages/doctor/lib/ios.js +++ b/packages/doctor/lib/ios.js @@ -1,9 +1,9 @@ -import { ok, nok, okOptional, nokOptional, resolveExecutablePath } from './utils'; // eslint-disable-line -import { fs } from '@appium/support'; -import { exec } from 'teen_process'; -import { DoctorCheck, FixSkippedError } from './doctor'; +import {ok, nok, okOptional, nokOptional, resolveExecutablePath} from './utils'; // eslint-disable-line +import {fs} from '@appium/support'; +import {exec} from 'teen_process'; +import {DoctorCheck, FixSkippedError} from './doctor'; import log from './logger'; -import { fixIt } from './prompt'; +import {fixIt} from './prompt'; import EnvVarAndPathCheck from './env'; import '@colors/colors'; @@ -12,7 +12,7 @@ let fixes = {}; // Check for Xcode. class XcodeCheck extends DoctorCheck { - async diagnose () { + async diagnose() { let xcodePath; try { // https://github.com/appium/appium/issues/12093#issuecomment-459358120 can happen @@ -26,23 +26,27 @@ class XcodeCheck extends DoctorCheck { } catch (err) { return nok('Xcode is NOT installed!'); } - return xcodePath && await fs.exists(xcodePath) ? ok(`Xcode is installed at: ${xcodePath}`) : - nok(`Xcode cannot be found at '${xcodePath}'!`); + return xcodePath && (await fs.exists(xcodePath)) + ? ok(`Xcode is installed at: ${xcodePath}`) + : nok(`Xcode cannot be found at '${xcodePath}'!`); } - async fix () { // eslint-disable-line require-await - return `Manually install ${'Xcode'.bold}, and make sure 'xcode-select -p' command shows proper path like '/Applications/Xcode.app/Contents/Developer'`; + // eslint-disable-next-line require-await + async fix() { + return `Manually install ${ + 'Xcode'.bold + }, and make sure 'xcode-select -p' command shows proper path like '/Applications/Xcode.app/Contents/Developer'`; } } checks.push(new XcodeCheck()); // Check for Xcode Command Line Tools. class XcodeCmdLineToolsCheck extends DoctorCheck { - constructor () { + constructor() { super({autofix: true}); } - async diagnose () { + async diagnose() { const errMess = 'Xcode Command Line Tools are NOT installed!'; try { // https://stackoverflow.com/questions/15371925/how-to-check-if-command-line-tools-is-installed @@ -54,7 +58,7 @@ class XcodeCmdLineToolsCheck extends DoctorCheck { } } - async fix () { + async fix() { log.info(`The following command need be executed: xcode-select --install`); let yesno = await fixIt(); if (yesno === 'yes') { @@ -70,7 +74,7 @@ checks.push(new XcodeCmdLineToolsCheck()); // Dev Tools Security class DevToolsSecurityCheck extends DoctorCheck { - async diagnose () { + async diagnose() { const errMess = 'DevToolsSecurity is NOT enabled!'; let stdout; try { @@ -79,8 +83,7 @@ class DevToolsSecurityCheck extends DoctorCheck { log.debug(err); return nok(errMess); } - return stdout && stdout.match(/enabled/) ? ok('DevToolsSecurity is enabled.') - : nok(errMess); + return stdout && stdout.match(/enabled/) ? ok('DevToolsSecurity is enabled.') : nok(errMess); } } checks.push(new DevToolsSecurityCheck()); @@ -88,7 +91,7 @@ checks.push(new DevToolsSecurityCheck()); checks.push(new EnvVarAndPathCheck('HOME')); class OptionalLyftCommandCheck extends DoctorCheck { - async diagnose () { + async diagnose() { const lyftCmd = await resolveExecutablePath('set-simulator-location'); if (lyftCmd) { return okOptional('set-simulator-location is installed'); @@ -96,17 +99,18 @@ class OptionalLyftCommandCheck extends DoctorCheck { return nokOptional('set-simulator-location is not installed'); } - - async fix () { // eslint-disable-line require-await - return `${'set-simulator-location'.bold} is needed to set location for Simulator. ` + - 'Please read https://github.com/lyft/set-simulator-location to install it'; + // eslint-disable-next-line require-await + async fix() { + return ( + `${'set-simulator-location'.bold} is needed to set location for Simulator. ` + + 'Please read https://github.com/lyft/set-simulator-location to install it' + ); } } checks.push(new OptionalLyftCommandCheck()); - class OptionalIdbCommandCheck extends DoctorCheck { - async diagnose () { + async diagnose() { const fbIdbPath = await resolveExecutablePath('idb'); const fbCompanionIdbPath = await resolveExecutablePath('idb_companion'); if (fbIdbPath && fbCompanionIdbPath) { @@ -120,9 +124,11 @@ class OptionalIdbCommandCheck extends DoctorCheck { } return nokOptional('idb and idb_companion are not installed'); } - - async fix () { // eslint-disable-line require-await - return `Why ${'idb'.bold} is needed and how to install it: ${OptionalIdbCommandCheck.idbReadmeURL}`; + // eslint-disable-next-line require-await + async fix() { + return `Why ${'idb'.bold} is needed and how to install it: ${ + OptionalIdbCommandCheck.idbReadmeURL + }`; } } // link to idb README.md @@ -131,36 +137,55 @@ OptionalIdbCommandCheck.idbReadmeURL = 'https://git.io/JnxQc'; checks.push(new OptionalIdbCommandCheck()); class OptionalApplesimutilsCommandCheck extends DoctorCheck { - async diagnose () { + async diagnose() { const applesimutilsPath = await resolveExecutablePath('applesimutils'); return applesimutilsPath - ? okOptional(`applesimutils is installed at: ${applesimutilsPath}. Installed versions are: ${(await exec('brew', ['list', '--versions', 'applesimutils'])).stdout.trim()}`) + ? okOptional( + `applesimutils is installed at: ${applesimutilsPath}. Installed versions are: ${( + await exec('brew', ['list', '--versions', 'applesimutils']) + ).stdout.trim()}` + ) : nokOptional('applesimutils cannot be found'); } - async fix () { // eslint-disable-line require-await - return `Why ${'applesimutils'.bold} is needed and how to install it: http://appium.io/docs/en/drivers/ios-xcuitest/`; + // eslint-disable-next-line require-await + async fix() { + return `Why ${ + 'applesimutils'.bold + } is needed and how to install it: http://appium.io/docs/en/drivers/ios-xcuitest/`; } } checks.push(new OptionalApplesimutilsCommandCheck()); class OptionalIOSDeployCommandCheck extends DoctorCheck { - async diagnose () { + async diagnose() { const iosDeployPath = await resolveExecutablePath('ios-deploy'); return iosDeployPath - ? okOptional(`ios-deploy is installed at: ${iosDeployPath}. Installed version is: ${(await exec(iosDeployPath, ['-V'])).stdout.trim()}`) + ? okOptional( + `ios-deploy is installed at: ${iosDeployPath}. Installed version is: ${( + await exec(iosDeployPath, ['-V']) + ).stdout.trim()}` + ) : nokOptional('ios-deploy cannot be found'); } - async fix () { // eslint-disable-line require-await - return `${'ios-deploy'.bold} is used as a fallback command to install iOS applications to real device. Please read https://github.com/ios-control/ios-deploy/ to install it`; + // eslint-disable-next-line require-await + async fix() { + return `${ + 'ios-deploy'.bold + } is used as a fallback command to install iOS applications to real device. Please read https://github.com/ios-control/ios-deploy/ to install it`; } } checks.push(new OptionalIOSDeployCommandCheck()); export { - fixes, XcodeCheck, XcodeCmdLineToolsCheck, DevToolsSecurityCheck, - OptionalIdbCommandCheck, OptionalApplesimutilsCommandCheck, - OptionalIOSDeployCommandCheck, OptionalLyftCommandCheck + fixes, + XcodeCheck, + XcodeCmdLineToolsCheck, + DevToolsSecurityCheck, + OptionalIdbCommandCheck, + OptionalApplesimutilsCommandCheck, + OptionalIOSDeployCommandCheck, + OptionalLyftCommandCheck, }; export default checks; diff --git a/packages/doctor/lib/logger.js b/packages/doctor/lib/logger.js index 1a42c99f5..acb879127 100644 --- a/packages/doctor/lib/logger.js +++ b/packages/doctor/lib/logger.js @@ -1,5 +1,4 @@ -import { logger } from '@appium/support'; - +import {logger} from '@appium/support'; const log = logger.getLogger('AppiumDoctor'); diff --git a/packages/doctor/lib/node-detector.js b/packages/doctor/lib/node-detector.js index 78cece45e..5b8281485 100644 --- a/packages/doctor/lib/node-detector.js +++ b/packages/doctor/lib/node-detector.js @@ -1,29 +1,25 @@ -import { fs, system } from '@appium/support'; -import { exec } from 'teen_process'; +import {fs, system} from '@appium/support'; +import {exec} from 'teen_process'; import log from './logger'; import path from 'path'; -import { resolveExecutablePath } from './utils'; +import {resolveExecutablePath} from './utils'; -const NODE_COMMON_PATHS = [ - process.env.NODE_BIN, - '/usr/local/bin/node', - '/opt/local/bin/node', -]; +const NODE_COMMON_PATHS = [process.env.NODE_BIN, '/usr/local/bin/node', '/opt/local/bin/node']; // Look for node class NodeDetector { - static async retrieveInCommonPlaces () { + static async retrieveInCommonPlaces() { for (let p of NODE_COMMON_PATHS) { - if (p && await fs.exists(p)) { + if (p && (await fs.exists(p))) { log.debug(`Node binary found at common place: ${p}`); return p; } } - log.debug('Node binary wasn\'t found at common places.'); + log.debug("Node binary wasn't found at common places."); return null; } - static async retrieveUsingSystemCall () { + static async retrieveUsingSystemCall() { const nodePath = await resolveExecutablePath('node'); if (!nodePath) { @@ -35,23 +31,23 @@ class NodeDetector { return nodePath; } - static async retrieveUsingAppleScript () { + static async retrieveUsingAppleScript() { if (!system.isMac()) { log.debug('Not on Darwin, skipping Apple Script'); return null; } const appScript = [ - 'try' - , ' set appiumIsRunning to false' - , ' tell application "System Events"' - , ' set appiumIsRunning to name of every process contains "Appium"' - , ' end tell' - , ' if appiumIsRunning then' - , ' tell application "Appium" to return node path' - , ' end if' - , 'end try' - , 'return "NULL"' + 'try', + ' set appiumIsRunning to false', + ' tell application "System Events"', + ' set appiumIsRunning to name of every process contains "Appium"', + ' end tell', + ' if appiumIsRunning then', + ' tell application "Appium" to return node path', + ' end if', + 'end try', + 'return "NULL"', ].join('\n'); let stdout; try { @@ -70,7 +66,7 @@ class NodeDetector { } } - static async retrieveUsingAppiumConfigFile () { + static async retrieveUsingAppiumConfigFile() { let jsonobj; try { const appiumConfigPath = path.resolve(__dirname, '..', '..', '.appiumconfig.json'); @@ -81,7 +77,7 @@ class NodeDetector { log.debug(err); return null; } - if (jsonobj && jsonobj.node_bin && await fs.exists(jsonobj.node_bin)) { + if (jsonobj && jsonobj.node_bin && (await fs.exists(jsonobj.node_bin))) { log.debug(`Node binary found using .appiumconfig.json at: ${jsonobj.node_bin}`); return jsonobj.node_bin; } else { @@ -90,11 +86,12 @@ class NodeDetector { } } - static async detect () { - let nodePath = await NodeDetector.retrieveUsingSystemCall() || - await NodeDetector.retrieveInCommonPlaces() || - await NodeDetector.retrieveUsingAppleScript() || - await NodeDetector.retrieveUsingAppiumConfigFile(); + static async detect() { + let nodePath = + (await NodeDetector.retrieveUsingSystemCall()) || + (await NodeDetector.retrieveInCommonPlaces()) || + (await NodeDetector.retrieveUsingAppleScript()) || + (await NodeDetector.retrieveUsingAppiumConfigFile()); if (nodePath) { return nodePath; } else { diff --git a/packages/doctor/lib/prompt.js b/packages/doctor/lib/prompt.js index 4f9268e4e..caa64ed6c 100644 --- a/packages/doctor/lib/prompt.js +++ b/packages/doctor/lib/prompt.js @@ -1,4 +1,4 @@ -import { inquirer } from './utils'; +import {inquirer} from './utils'; let persistentResponse; @@ -7,12 +7,12 @@ const fixItQuestion = { name: 'confirmation', message: 'Fix it:', choices: ['yes', 'no', 'always', 'never'], - filter (val) { + filter(val) { return val.toLowerCase(); - } + }, }; -function configure (opts) { +function configure(opts) { if (opts.yes) { persistentResponse = 'yes'; } @@ -21,11 +21,11 @@ function configure (opts) { } } -function clear () { +function clear() { persistentResponse = undefined; } -async function fixIt () { +async function fixIt() { if (persistentResponse) { return persistentResponse; } @@ -35,4 +35,4 @@ async function fixIt () { return persistentResponse || resp.confirmation; } -export { configure, fixIt, clear }; +export {configure, fixIt, clear}; diff --git a/packages/doctor/lib/utils.js b/packages/doctor/lib/utils.js index 9530a4a6f..cf4760c2e 100644 --- a/packages/doctor/lib/utils.js +++ b/packages/doctor/lib/utils.js @@ -1,32 +1,35 @@ import B from 'bluebird'; import _inquirer from 'inquirer'; import log from '../lib/logger'; -import { fs, system } from '@appium/support'; -import { exec } from 'teen_process'; -import { isFunction } from 'lodash'; +import {fs, system} from '@appium/support'; +import {exec} from 'teen_process'; +import {isFunction} from 'lodash'; -function ok (message) { +function ok(message) { return {ok: true, optional: false, message}; } -function nok (message) { +function nok(message) { return {ok: false, optional: false, message}; } -function okOptional (message) { +function okOptional(message) { return {ok: true, optional: true, message}; } -function nokOptional (message) { +function nokOptional(message) { return {ok: false, optional: true, message}; } const inquirer = { - prompt: B.promisify(function (question, cb) { // eslint-disable-line promise/prefer-await-to-callbacks - _inquirer.prompt(question, function (resp) { cb(null, resp); }); // eslint-disable-line promise/prefer-await-to-callbacks - }) + prompt: B.promisify(function (question, cb) { + // eslint-disable-line promise/prefer-await-to-callbacks + _inquirer.prompt(question, function (resp) { + cb(null, resp); + }); // eslint-disable-line promise/prefer-await-to-callbacks + }), }; let actualLog; -function configureBinaryLog (opts) { +function configureBinaryLog(opts) { actualLog = log.unwrap().log; log.unwrap().log = function (level, prefix, msg) { let l = this.levels[level]; @@ -43,7 +46,7 @@ function configureBinaryLog (opts) { /** * If {@link configureBinaryLog} was called, this will restore the original `log` function. */ -function resetLog () { +function resetLog() { if (actualLog) { log.unwrap().log = actualLog; } @@ -55,15 +58,15 @@ function resetLog () { * @param {string} cmd Standard output by command * @return {?string} The full path of cmd. `null` if the cmd is not found. */ -async function resolveExecutablePath (cmd) { +async function resolveExecutablePath(cmd) { let executablePath; try { executablePath = await fs.which(cmd); - if (executablePath && await fs.exists(executablePath)) { + if (executablePath && (await fs.exists(executablePath))) { return executablePath; } } catch (err) { - if ((/not found/gi).test(err.message)) { + if (/not found/gi.test(err.message)) { log.debug(err); } else { log.warn(err); @@ -86,7 +89,7 @@ async function resolveExecutablePath (cmd) { * @param {string} packageName A package name to get path and version data * @return {?NpmPackageInfo} */ -async function getNpmPackageInfo (packageName) { +async function getNpmPackageInfo(packageName) { const npmPath = await resolveExecutablePath(`npm${system.isWindows() ? `.cmd` : ''}`); if (!npmPath) { return nokOptional(`'npm' binary not found in PATH: ${process.env.PATH}`); @@ -109,6 +112,13 @@ async function getNpmPackageInfo (packageName) { } export { - ok, nok, okOptional, nokOptional, inquirer, configureBinaryLog, - resolveExecutablePath, getNpmPackageInfo, resetLog + ok, + nok, + okOptional, + nokOptional, + inquirer, + configureBinaryLog, + resolveExecutablePath, + getNpmPackageInfo, + resetLog, }; diff --git a/packages/doctor/test/unit/android.spec.js b/packages/doctor/test/unit/android.spec.js index ac26d62e0..db484fb37 100644 --- a/packages/doctor/test/unit/android.spec.js +++ b/packages/doctor/test/unit/android.spec.js @@ -1,157 +1,181 @@ -import { EnvVarAndPathCheck, AndroidToolCheck, OptionalAppBundleCheck, OptionalGstreamerCheck } from '../../lib/android'; -import { fs } from '@appium/support'; +import { + EnvVarAndPathCheck, + AndroidToolCheck, + OptionalAppBundleCheck, + OptionalGstreamerCheck, +} from '../../lib/android'; +import {fs} from '@appium/support'; import * as adb from 'appium-adb'; import * as utils from '../../lib/utils'; import * as tp from 'teen_process'; -import { withMocks, stubEnv } from '@appium/test-support'; +import {withMocks, stubEnv} from '@appium/test-support'; import B from 'bluebird'; import {removeColors} from './helper'; - describe('android', function () { - describe('EnvVarAndPathCheck', withMocks({fs}, (mocks) => { - stubEnv(); - let check = new EnvVarAndPathCheck('ANDROID_HOME'); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - process.env.ANDROID_HOME = '/a/b/c/d'; - mocks.fs.expects('exists').once().returns(B.resolve(true)); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'ANDROID_HOME is set to: /a/b/c/d' + describe( + 'EnvVarAndPathCheck', + withMocks({fs}, (mocks) => { + stubEnv(); + let check = new EnvVarAndPathCheck('ANDROID_HOME'); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('failure - not set', async function () { - delete process.env.ANDROID_HOME; - const {ok, optional, message} = await check.diagnose(); - ok.should.be.false; - optional.should.be.false; - message.should.not.be.empty; - mocks.verify(); - }); - it('failure - file not exists', async function () { - process.env.ANDROID_HOME = '/a/b/c/d'; - mocks.fs.expects('exists').once().returns(B.resolve(false)); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'ANDROID_HOME is set to \'/a/b/c/d\' ' + - 'but this is NOT a valid path!' + it('diagnose - success', async function () { + process.env.ANDROID_HOME = '/a/b/c/d'; + mocks.fs.expects('exists').once().returns(B.resolve(true)); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: 'ANDROID_HOME is set to: /a/b/c/d', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.contain('ANDROID_HOME'); - }); - })); - describe('AndroidToolCheck', withMocks({adb}, (mocks) => { - stubEnv(); - const check = new AndroidToolCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - process.env.ANDROID_HOME = '/a/b/c/d'; - mocks.adb.expects('getAndroidBinaryPath').exactly(4).returns(B.resolve('/path/to/binary')); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'adb, android, emulator, apkanalyzer exist: /a/b/c/d' + it('failure - not set', async function () { + delete process.env.ANDROID_HOME; + const {ok, optional, message} = await check.diagnose(); + ok.should.be.false; + optional.should.be.false; + message.should.not.be.empty; + mocks.verify(); }); - mocks.verify(); - }); - it('diagnose - failure - no ANDROID_HOME', async function () { - delete process.env.ANDROID_HOME; - delete process.env.ANDROID_SDK_ROOT; - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'adb, android, emulator, apkanalyzer could not be found because ANDROID_HOME or ANDROID_SDK_ROOT is NOT set!' + it('failure - file not exists', async function () { + process.env.ANDROID_HOME = '/a/b/c/d'; + mocks.fs.expects('exists').once().returns(B.resolve(false)); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: "ANDROID_HOME is set to '/a/b/c/d' " + 'but this is NOT a valid path!', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('diagnose - failure - path not valid', async function () { - process.env.ANDROID_HOME = '/a/b/c/d'; - mocks.adb.expects('getAndroidBinaryPath').exactly(4).throws(); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'adb, android, emulator, apkanalyzer could NOT be found in /a/b/c/d!' + it('fix', async function () { + removeColors(await check.fix()).should.contain('ANDROID_HOME'); }); - mocks.verify(); - }); - it('fix - ANDROID_HOME', async function () { - delete process.env.ANDROID_HOME; - removeColors(await check.fix()).should.equal('Manually configure ANDROID_HOME ' + - 'and run appium-doctor again.'); - }); - it('fix - install', async function () { - process.env.ANDROID_HOME = '/a/b/c/d'; - removeColors(await check.fix()).should.equal('Manually install adb, android, emulator, apkanalyzer and add it to PATH. ' + - 'https://developer.android.com/studio#cmdline-tools and ' + - 'https://developer.android.com/studio/intro/update#sdk-manager may help to setup.'); - }); - })); + }) + ); + describe( + 'AndroidToolCheck', + withMocks({adb}, (mocks) => { + stubEnv(); + const check = new AndroidToolCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; + }); + it('diagnose - success', async function () { + process.env.ANDROID_HOME = '/a/b/c/d'; + mocks.adb.expects('getAndroidBinaryPath').exactly(4).returns(B.resolve('/path/to/binary')); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: 'adb, android, emulator, apkanalyzer exist: /a/b/c/d', + }); + mocks.verify(); + }); + it('diagnose - failure - no ANDROID_HOME', async function () { + delete process.env.ANDROID_HOME; + delete process.env.ANDROID_SDK_ROOT; + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: + 'adb, android, emulator, apkanalyzer could not be found because ANDROID_HOME or ANDROID_SDK_ROOT is NOT set!', + }); + mocks.verify(); + }); + it('diagnose - failure - path not valid', async function () { + process.env.ANDROID_HOME = '/a/b/c/d'; + mocks.adb.expects('getAndroidBinaryPath').exactly(4).throws(); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'adb, android, emulator, apkanalyzer could NOT be found in /a/b/c/d!', + }); + mocks.verify(); + }); + it('fix - ANDROID_HOME', async function () { + delete process.env.ANDROID_HOME; + removeColors(await check.fix()).should.equal( + 'Manually configure ANDROID_HOME ' + 'and run appium-doctor again.' + ); + }); + it('fix - install', async function () { + process.env.ANDROID_HOME = '/a/b/c/d'; + removeColors(await check.fix()).should.equal( + 'Manually install adb, android, emulator, apkanalyzer and add it to PATH. ' + + 'https://developer.android.com/studio#cmdline-tools and ' + + 'https://developer.android.com/studio/intro/update#sdk-manager may help to setup.' + ); + }); + }) + ); - describe('OptionalAppBundleCheck', withMocks({tp, utils}, (mocks) => { - let check = new OptionalAppBundleCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/bundletool.jar'); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: true, - message: 'bundletool.jar is installed at: path/to/bundletool.jar' + describe( + 'OptionalAppBundleCheck', + withMocks({tp, utils}, (mocks) => { + let check = new OptionalAppBundleCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('diagnose - failure', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns(false); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: true, - message: 'bundletool.jar cannot be found' + it('diagnose - success', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns('path/to/bundletool.jar'); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: true, + message: 'bundletool.jar is installed at: path/to/bundletool.jar', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal('bundletool.jar is used to handle Android App Bundle. Please read http://appium.io/docs/en/writing-running-appium/android/android-appbundle/ to install it'); - }); - })); + it('diagnose - failure', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns(false); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: true, + message: 'bundletool.jar cannot be found', + }); + mocks.verify(); + }); + it('fix', async function () { + removeColors(await check.fix()).should.equal( + 'bundletool.jar is used to handle Android App Bundle. Please read http://appium.io/docs/en/writing-running-appium/android/android-appbundle/ to install it' + ); + }); + }) + ); - describe('OptionalGstreamerCheck', withMocks({tp, utils}, (mocks) => { - let check = new OptionalGstreamerCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/gst-launch'); - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/gst-inspect'); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: true, - message: 'gst-launch-1.0 and gst-inspect-1.0 are installed at: path/to/gst-launch and path/to/gst-inspect' + describe( + 'OptionalGstreamerCheck', + withMocks({tp, utils}, (mocks) => { + let check = new OptionalGstreamerCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('diagnose - failure', async function () { - mocks.utils.expects('resolveExecutablePath').twice().returns(false); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: true, - message: 'gst-launch-1.0 and/or gst-inspect-1.0 cannot be found' + it('diagnose - success', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns('path/to/gst-launch'); + mocks.utils.expects('resolveExecutablePath').once().returns('path/to/gst-inspect'); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: true, + message: + 'gst-launch-1.0 and gst-inspect-1.0 are installed at: path/to/gst-launch and path/to/gst-inspect', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal('gst-launch-1.0 and gst-inspect-1.0 are used to stream the screen of the device under test. ' + - 'Please read https://appium.io/docs/en/writing-running-appium/android/android-screen-streaming/ to install them and for more details'); - }); - })); + it('diagnose - failure', async function () { + mocks.utils.expects('resolveExecutablePath').twice().returns(false); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: true, + message: 'gst-launch-1.0 and/or gst-inspect-1.0 cannot be found', + }); + mocks.verify(); + }); + it('fix', async function () { + removeColors(await check.fix()).should.equal( + 'gst-launch-1.0 and gst-inspect-1.0 are used to stream the screen of the device under test. ' + + 'Please read https://appium.io/docs/en/writing-running-appium/android/android-screen-streaming/ to install them and for more details' + ); + }); + }) + ); }); diff --git a/packages/doctor/test/unit/demo.spec.js b/packages/doctor/test/unit/demo.spec.js index 6565c9e7d..f48dfbf4c 100644 --- a/packages/doctor/test/unit/demo.spec.js +++ b/packages/doctor/test/unit/demo.spec.js @@ -1,104 +1,126 @@ -import { DirCheck, FileCheck } from '../../lib/demo'; -import { fs } from '@appium/support'; +import {DirCheck, FileCheck} from '../../lib/demo'; +import {fs} from '@appium/support'; import * as tp from 'teen_process'; import * as prompt from '../../lib/prompt'; import log from '../../lib/logger'; -import { FixSkippedError } from '../../lib/doctor'; -import { withMocks, withSandbox, stubLog } from '@appium/test-support'; +import {FixSkippedError} from '../../lib/doctor'; +import {withMocks, withSandbox, stubLog} from '@appium/test-support'; import B from 'bluebird'; - - describe('demo', function () { - describe('DirCheck', withMocks({fs}, (mocks) => { - let check = new DirCheck('/a/b/c/d'); + describe( + 'DirCheck', + withMocks({fs}, (mocks) => { + let check = new DirCheck('/a/b/c/d'); - it('diagnose - success', async function () { - mocks.fs.expects('exists').once().returns(B.resolve(true)); - mocks.fs.expects('lstat').once().returns( - B.resolve({ - isDirectory () { return true; } - })); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'Found directory at: /a/b/c/d' + it('diagnose - success', async function () { + mocks.fs.expects('exists').once().returns(B.resolve(true)); + mocks.fs + .expects('lstat') + .once() + .returns( + B.resolve({ + isDirectory() { + return true; + }, + }) + ); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: 'Found directory at: /a/b/c/d', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('failure - not there', async function () { - mocks.fs.expects('exists').once().returns(B.resolve(false)); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'Could NOT find directory at \'/a/b/c/d\'!' + it('failure - not there', async function () { + mocks.fs.expects('exists').once().returns(B.resolve(false)); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: "Could NOT find directory at '/a/b/c/d'!", + }); + mocks.verify(); }); - mocks.verify(); - }); - it('failure - not a dir', async function () { - mocks.fs.expects('exists').once().returns(B.resolve(true)); - mocks.fs.expects('lstat').once().returns( - B.resolve({ - isDirectory () { return false; } - })); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: '\'/a/b/c/d\' is NOT a directory!' + it('failure - not a dir', async function () { + mocks.fs.expects('exists').once().returns(B.resolve(true)); + mocks.fs + .expects('lstat') + .once() + .returns( + B.resolve({ + isDirectory() { + return false; + }, + }) + ); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: "'/a/b/c/d' is NOT a directory!", + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - (await check.fix()).should.equal('Manually create a directory at: /a/b/c/d'); - }); - })); - - describe('FileCheck', withSandbox({mocks: {fs, tp, prompt}}, (S) => { - let check = new FileCheck('/a/b/c/d'); - - it('diagnose - success', async function () { - S.mocks.fs.expects('exists').once().returns(B.resolve(true)); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'Found file at: /a/b/c/d' + it('fix', async function () { + (await check.fix()).should.equal('Manually create a directory at: /a/b/c/d'); }); - S.verify(); - }); + }) + ); - it('failure - not there', async function () { - S.mocks.fs.expects('exists').once().returns(B.resolve(false)); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'Could NOT find file at \'/a/b/c/d\'!' + describe( + 'FileCheck', + withSandbox({mocks: {fs, tp, prompt}}, (S) => { + let check = new FileCheck('/a/b/c/d'); + + it('diagnose - success', async function () { + S.mocks.fs.expects('exists').once().returns(B.resolve(true)); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: 'Found file at: /a/b/c/d', + }); + S.verify(); }); - S.verify(); - }); - it('fix - yes', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - S.mocks.prompt.expects('fixIt').once().returns(B.resolve('yes')); - S.mocks.tp.expects('exec').once().returns( - B.resolve({stdout: '', stderr: ''})); - (await check.fix()); - S.verify(); - logStub.output.should.equal('info: The following command need be executed: touch \'/a/b/c/d\''); - }); + it('failure - not there', async function () { + S.mocks.fs.expects('exists').once().returns(B.resolve(false)); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: "Could NOT find file at '/a/b/c/d'!", + }); + S.verify(); + }); - it('fix - no', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - S.mocks.prompt.expects('fixIt').once().returns(B.resolve('no')); - S.mocks.tp.expects('exec').never(); - await check.fix().should.be.rejectedWith(FixSkippedError); - S.verify(); - logStub.output.should.equal([ - 'info: The following command need be executed: touch \'/a/b/c/d\'', - 'info: Skipping you will need to touch \'/a/b/c/d\' manually.' - ].join('\n')); - }); - })); + it('fix - yes', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + S.mocks.prompt.expects('fixIt').once().returns(B.resolve('yes')); + S.mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: '', stderr: ''})); + await check.fix(); + S.verify(); + logStub.output.should.equal( + "info: The following command need be executed: touch '/a/b/c/d'" + ); + }); + + it('fix - no', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + S.mocks.prompt.expects('fixIt').once().returns(B.resolve('no')); + S.mocks.tp.expects('exec').never(); + await check.fix().should.be.rejectedWith(FixSkippedError); + S.verify(); + logStub.output.should.equal( + [ + "info: The following command need be executed: touch '/a/b/c/d'", + "info: Skipping you will need to touch '/a/b/c/d' manually.", + ].join('\n') + ); + }); + }) + ); }); diff --git a/packages/doctor/test/unit/dev.spec.js b/packages/doctor/test/unit/dev.spec.js index fbb3bc3e4..2cf38f86c 100644 --- a/packages/doctor/test/unit/dev.spec.js +++ b/packages/doctor/test/unit/dev.spec.js @@ -1,98 +1,106 @@ -import { BinaryIsInPathCheck, AndroidSdkExists } from '../../lib/dev'; -import { fs } from '@appium/support'; +import {BinaryIsInPathCheck, AndroidSdkExists} from '../../lib/dev'; +import {fs} from '@appium/support'; import * as tp from 'teen_process'; -import { withMocks, stubEnv } from '@appium/test-support'; +import {withMocks, stubEnv} from '@appium/test-support'; import B from 'bluebird'; -import { removeColors } from './helper'; - +import {removeColors} from './helper'; describe('dev', function () { - describe('BinaryIsInPathCheck', withMocks({tp, fs}, (mocks) => { - stubEnv(); - let check = new BinaryIsInPathCheck('mvn'); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - process.env.PATH = '/a/b/c/d;/e/f/g/h'; - mocks.fs.expects('which').once().returns(B.resolve('/a/b/c/d/mvn')); - mocks.fs.expects('exists').once().returns(B.resolve(true)); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'mvn was found at /a/b/c/d/mvn' + describe( + 'BinaryIsInPathCheck', + withMocks({tp, fs}, (mocks) => { + stubEnv(); + let check = new BinaryIsInPathCheck('mvn'); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('diagnose - failure - not in path ', async function () { - process.env.PATH = '/a/b/c/d;/e/f/g/h'; - mocks.fs.expects('which').once().returns( - B.resolve({stack: 'mvn not found'})); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'mvn is MISSING in PATH: /a/b/c/d;/e/f/g/h' + it('diagnose - success', async function () { + process.env.PATH = '/a/b/c/d;/e/f/g/h'; + mocks.fs.expects('which').once().returns(B.resolve('/a/b/c/d/mvn')); + mocks.fs.expects('exists').once().returns(B.resolve(true)); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: 'mvn was found at /a/b/c/d/mvn', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('diagnose - failure - invalid path', async function () { - process.env.PATH = '/a/b/c/d;/e/f/g/h'; - mocks.fs.expects('which').once().returns( - B.resolve('/a/b/c/d/mvn')); - mocks.fs.expects('exists').once().returns(B.resolve(false)); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'mvn is MISSING in PATH: /a/b/c/d;/e/f/g/h' + it('diagnose - failure - not in path ', async function () { + process.env.PATH = '/a/b/c/d;/e/f/g/h'; + mocks.fs + .expects('which') + .once() + .returns(B.resolve({stack: 'mvn not found'})); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'mvn is MISSING in PATH: /a/b/c/d;/e/f/g/h', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal('Manually install the mvn binary and add it to PATH.'); - }); - })); - describe('AndroidSdkExists', withMocks({fs}, (mocks) => { - stubEnv(); - let check = new AndroidSdkExists('android-16'); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - process.env.ANDROID_HOME = '/a/b/c/d'; - mocks.fs.expects('exists').once().returns(B.resolve(true)); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'android-16 was found at: /a/b/c/d/platforms/android-16' + it('diagnose - failure - invalid path', async function () { + process.env.PATH = '/a/b/c/d;/e/f/g/h'; + mocks.fs.expects('which').once().returns(B.resolve('/a/b/c/d/mvn')); + mocks.fs.expects('exists').once().returns(B.resolve(false)); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'mvn is MISSING in PATH: /a/b/c/d;/e/f/g/h', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('failure - missing android home', async function () { - delete process.env.ANDROID_HOME; - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'android-16 could not be found because ANDROID_HOME is NOT set!' + it('fix', async function () { + removeColors(await check.fix()).should.equal( + 'Manually install the mvn binary and add it to PATH.' + ); }); - mocks.verify(); - }); - it('diagnose - failure - invalid path', async function () { - process.env.ANDROID_HOME = '/a/b/c/d'; - mocks.fs.expects('exists').once().returns(B.resolve(false)); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'android-16 could NOT be found at \'/a/b/c/d/platforms/android-16\'!' + }) + ); + describe( + 'AndroidSdkExists', + withMocks({fs}, (mocks) => { + stubEnv(); + let check = new AndroidSdkExists('android-16'); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('fix - ANDROID_HOME', async function () { - delete process.env.ANDROID_HOME; - removeColors(await check.fix()).should.equal('Manually configure ANDROID_HOME.'); - }); - it('fix - install', async function () { - process.env.ANDROID_HOME = '/a/b/c/d'; - removeColors(await check.fix()).should.equal('Manually install the android-16 sdk.'); - }); - })); + it('diagnose - success', async function () { + process.env.ANDROID_HOME = '/a/b/c/d'; + mocks.fs.expects('exists').once().returns(B.resolve(true)); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: 'android-16 was found at: /a/b/c/d/platforms/android-16', + }); + mocks.verify(); + }); + it('failure - missing android home', async function () { + delete process.env.ANDROID_HOME; + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'android-16 could not be found because ANDROID_HOME is NOT set!', + }); + mocks.verify(); + }); + it('diagnose - failure - invalid path', async function () { + process.env.ANDROID_HOME = '/a/b/c/d'; + mocks.fs.expects('exists').once().returns(B.resolve(false)); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: "android-16 could NOT be found at '/a/b/c/d/platforms/android-16'!", + }); + mocks.verify(); + }); + it('fix - ANDROID_HOME', async function () { + delete process.env.ANDROID_HOME; + removeColors(await check.fix()).should.equal('Manually configure ANDROID_HOME.'); + }); + it('fix - install', async function () { + process.env.ANDROID_HOME = '/a/b/c/d'; + removeColors(await check.fix()).should.equal('Manually install the android-16 sdk.'); + }); + }) + ); }); diff --git a/packages/doctor/test/unit/doctor.spec.js b/packages/doctor/test/unit/doctor.spec.js index d9a7a5a2e..8bb0f66e9 100644 --- a/packages/doctor/test/unit/doctor.spec.js +++ b/packages/doctor/test/unit/doctor.spec.js @@ -1,10 +1,8 @@ -import { Doctor, DoctorCheck, FixSkippedError } from '../../lib/doctor'; -import { withSandbox, stubLog } from '@appium/test-support'; +import {Doctor, DoctorCheck, FixSkippedError} from '../../lib/doctor'; +import {withSandbox, stubLog} from '@appium/test-support'; import log from '../../lib/logger'; import B from 'bluebird'; - - describe('doctor', function () { it('register', function () { let doctor = new Doctor(); @@ -15,326 +13,419 @@ describe('doctor', function () { doctor.checks.should.have.length(3); }); - function configure () { + function configure() { let doctor = new Doctor(); - let checks = [new DoctorCheck(), new DoctorCheck(), new DoctorCheck(), new DoctorCheck(), new DoctorCheck()]; + let checks = [ + new DoctorCheck(), + new DoctorCheck(), + new DoctorCheck(), + new DoctorCheck(), + new DoctorCheck(), + ]; doctor.register(checks); return {doctor, checks}; } - describe('diagnose', withSandbox({}, (S) => { - it('should detect all issues', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - let {doctor, checks} = configure(); - S.mocks.checks = checks.map((check) => S.sandbox.mock(check)); - S.mocks.checks[0].expects('diagnose').once().returns({ok: true, message: 'All Good!'}); - S.mocks.checks[1].expects('diagnose').twice().returns({ok: true, optional: true, message: 'All Good Option!'}); - S.mocks.checks[2].expects('diagnose').once().returns({ok: false, message: 'Oh No!'}); - S.mocks.checks[3].expects('diagnose').twice().returns({ok: false, optional: true, message: 'Oh No Option!'}); - S.mocks.checks[4].expects('diagnose').once().returns({ok: false, message: 'Oh No!'}); - await doctor.diagnose(); - S.verify(); - doctor.toFix.should.have.length(2); - doctor.toFixOptionals.should.have.length(1); - logStub.output.should.equal([ - 'info: ### Diagnostic for necessary dependencies starting ###', - 'info: ✔ All Good!', - 'warn: ✖ Oh No!', - 'warn: ✖ Oh No!', - 'info: ### Diagnostic for necessary dependencies completed, 2 fixes needed. ###', - 'info: ', - 'info: ### Diagnostic for optional dependencies starting ###', - 'info: ✔ All Good Option!', - 'warn: ✖ Oh No Option!', - 'info: ### Diagnostic for optional dependencies completed, one fix possible. ###', - 'info: ', - ].join('\n')); - }); - })); - - describe('reportSuccess', withSandbox({}, (S) => { - let doctor = new Doctor(); - it('should report success when no fixes are needed', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - (await doctor.reportSuccess(doctor.toFix.length, doctor.toFixOptionals.length)).should.equal(true); - logStub.output.should.equal([ - 'info: Everything looks good, bye!', - 'info: ' - ].join('\n')); - }); - - it('should return false when fixes are needed', async function () { - doctor.toFix = [{}]; - (await doctor.reportSuccess(doctor.toFix.length)).should.equal(false); - }); - })); - - describe('reportManualFixes', withSandbox({}, (S) => { - let doctor = new Doctor(); - it('should ask for manual fixes to be applied', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - doctor.toFix = [ - {error: 'Oh no this need to be manually fixed.', check: new DoctorCheck()}, - {error: 'Oh no this is an autofix.', check: new DoctorCheck({autofix: true})}, - {error: 'Oh no this also need to be manually fixed.', check: new DoctorCheck()}, - {error: 'Oh no this also need to be manually fixed.', check: new DoctorCheck()}, - ]; - doctor.toFixOptionals = []; - for (let i = 0; i < doctor.toFix.length; i++) { - let m = S.sandbox.mock(doctor.toFix[i].check); - if (doctor.toFix[i].check.autofix) { - m.expects('fix').never(); - } else { - m.expects('fix').once().returns(B.resolve(`Manual fix for ${i} is do something.`)); - } - } - (await doctor.reportManualFixes(doctor.toFix, doctor.toFixOptionals)).should.equal(true); - S.verify(); - logStub.output.should.equal([ - 'info: ### Manual Fixes Needed ###', - 'info: The configuration cannot be automatically fixed, please do the following first:', - 'warn: ➜ Manual fix for 0 is do something.', - 'warn: ➜ Manual fix for 2 is do something.', - 'warn: ➜ Manual fix for 3 is do something.', - 'info: ', - 'info: ###', - 'info: ', - 'info: Bye! Run appium-doctor again when all manual fixes have been applied!', - 'info: ' - ].join('\n')); - }); - - it('should ask for manual fixes to be applied for optional', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - doctor.toFix = []; - doctor.toFixOptionals = [ - {error: 'Oh no this need to be manually fixed.', check: new DoctorCheck()}, - {error: 'Oh no this also need to be manually fixed.', check: new DoctorCheck()}, - ]; - for (let i = 0; i < doctor.toFixOptionals.length; i++) { - let m = S.sandbox.mock(doctor.toFixOptionals[i].check); - if (doctor.toFixOptionals[i].check.autofix) { - m.expects('fix').never(); - } else { - m.expects('fix').once().returns(B.resolve(`Manual fix for ${i} is do something.`)); - } - } - (await doctor.reportManualFixes(doctor.toFix, doctor.toFixOptionals)).should.equal(true); - S.verify(); - logStub.output.should.equal([ - 'info: ### Optional Manual Fixes ###', - 'info: The configuration can install optionally. Please do the following manually:', - 'warn: ➜ Manual fix for 0 is do something.', - 'warn: ➜ Manual fix for 1 is do something.', - 'info: ', - 'info: ###', - 'info: ', - 'info: Bye! Run appium-doctor again when all manual fixes have been applied!', - 'info: ' - ].join('\n')); - }); - - it('should ask for manual fixes to be applied for necessary and optional', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - doctor.toFix = [ - {error: 'Oh no this need to be manually fixed.', check: new DoctorCheck()} - ]; - doctor.toFixOptionals = [ - {error: 'Oh no this need to be manually fixed, but it is optional.', check: new DoctorCheck()}, - ]; - - S.sandbox.mock(doctor.toFix[0].check).expects('fix').once().returns(B.resolve(`Manual fix for 0 is do something.`)); - S.sandbox.mock(doctor.toFixOptionals[0].check).expects('fix').once().returns(B.resolve(`Manual fix for 0 is do something.`)); - - (await doctor.reportManualFixes(doctor.toFix, doctor.toFixOptionals)).should.equal(true); - S.verify(); - logStub.output.should.equal([ - 'info: ### Manual Fixes Needed ###', - 'info: The configuration cannot be automatically fixed, please do the following first:', - 'warn: ➜ Manual fix for 0 is do something.', - 'info: ', - 'info: ### Optional Manual Fixes ###', - 'info: The configuration can install optionally. Please do the following manually:', - 'warn: ➜ Manual fix for 0 is do something.', - 'info: ', - 'info: ###', - 'info: ', - 'info: Bye! Run appium-doctor again when all manual fixes have been applied!', - 'info: ' - ].join('\n')); - }); - - it('should return false when there is no manual fix', async function () { - doctor.toFix = [{error: 'Oh no!', check: new DoctorCheck({autofix: true}) }]; - (await doctor.reportManualFixes()).should.equal(false); - }); - })); - - describe('runAutoFix', withSandbox({}, (S) => { - let doctor = new Doctor(); - let fix = { - error: 'Something wrong!', - check: { - fix () {}, - diagnose () {} - } - }; - - it('fix - success', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - S.mocks.check = S.sandbox.mock(fix.check); - S.mocks.check.expects('fix').once(); - S.mocks.check.expects('diagnose').once().returns(B.resolve({ - ok: true, - optional: false, - message: 'It worked' - })); - await doctor.runAutoFix(fix); - S.verify(); - logStub.output.should.equal([ - 'info: ### Fixing: Something wrong! ###', - 'info: Checking if this was fixed:', - 'info: ✔ It worked', - 'info: ### Fix was successfully applied ###' - ].join('\n')); - }); - - it('fix - skipped', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - S.mocks.check = S.sandbox.mock(fix.check); - S.mocks.check.expects('fix').once().throws(new FixSkippedError()); - await doctor.runAutoFix(fix); - S.verify(); - logStub.output.should.equal([ - 'info: ### Fixing: Something wrong! ###', - 'info: ### Skipped fix ###', - ].join('\n')); - }); - - it('fix - crash', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - S.mocks.check = S.sandbox.mock(fix.check); - S.mocks.check.expects('fix').once().throws(new Error('Oh No!')); - await doctor.runAutoFix(fix); - S.verify(); - logStub.output.should.equal([ - 'info: ### Fixing: Something wrong! ###', - 'warn: Error: Oh No!', - 'info: ### Fix did not succeed ###', - ].join('\n')); - }); - - it('fix - didn\'t fix', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - S.mocks.check = S.sandbox.mock(fix.check); - S.mocks.check.expects('fix').once(); - S.mocks.check.expects('diagnose').once().returns(B.resolve({ - ok: false, message: 'Still Weird!'})); - await doctor.runAutoFix(fix); - S.verify(); - logStub.output.should.equal([ - 'info: ### Fixing: Something wrong! ###', - 'info: Checking if this was fixed:', - 'info: ✖ Still Weird!', - 'info: ### Fix was applied but issue remains ###' - ].join('\n')); - }); - })); - - describe('runAutoFixes', withSandbox({}, (S) => { - let doctor = new Doctor(); - it('success', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - doctor.toFix = [ - {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, - {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, - {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, - ]; - S.sandbox.stub(doctor, 'runAutoFix').callsFake((f) => { - log.info('Autofix log go there.'); - f.fixed = true; + describe( + 'diagnose', + withSandbox({}, (S) => { + it('should detect all issues', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + let {doctor, checks} = configure(); + S.mocks.checks = checks.map((check) => S.sandbox.mock(check)); + S.mocks.checks[0].expects('diagnose').once().returns({ok: true, message: 'All Good!'}); + S.mocks.checks[1] + .expects('diagnose') + .twice() + .returns({ok: true, optional: true, message: 'All Good Option!'}); + S.mocks.checks[2].expects('diagnose').once().returns({ok: false, message: 'Oh No!'}); + S.mocks.checks[3] + .expects('diagnose') + .twice() + .returns({ok: false, optional: true, message: 'Oh No Option!'}); + S.mocks.checks[4].expects('diagnose').once().returns({ok: false, message: 'Oh No!'}); + await doctor.diagnose(); + S.verify(); + doctor.toFix.should.have.length(2); + doctor.toFixOptionals.should.have.length(1); + logStub.output.should.equal( + [ + 'info: ### Diagnostic for necessary dependencies starting ###', + 'info: ✔ All Good!', + 'warn: ✖ Oh No!', + 'warn: ✖ Oh No!', + 'info: ### Diagnostic for necessary dependencies completed, 2 fixes needed. ###', + 'info: ', + 'info: ### Diagnostic for optional dependencies starting ###', + 'info: ✔ All Good Option!', + 'warn: ✖ Oh No Option!', + 'info: ### Diagnostic for optional dependencies completed, one fix possible. ###', + 'info: ', + ].join('\n') + ); }); - await doctor.runAutoFixes(); - doctor.runAutoFix.calledThrice.should.be.ok; - logStub.output.should.equal([ - 'info: Autofix log go there.', - 'info: ', - 'info: Autofix log go there.', - 'info: ', - 'info: Autofix log go there.', - 'info: ', - 'info: Bye! All issues have been fixed!', - 'info: ', - ].join('\n')); - }); + }) + ); - it('failure', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - doctor.toFix = [ - {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, - {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, - {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, - ]; - let succeed = false; - S.sandbox.stub(doctor, 'runAutoFix').callsFake((f) => { - if (succeed) { - log.info('succeeded, Autofix log go there.'); + describe( + 'reportSuccess', + withSandbox({}, (S) => { + let doctor = new Doctor(); + it('should report success when no fixes are needed', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + ( + await doctor.reportSuccess(doctor.toFix.length, doctor.toFixOptionals.length) + ).should.equal(true); + logStub.output.should.equal(['info: Everything looks good, bye!', 'info: '].join('\n')); + }); + + it('should return false when fixes are needed', async function () { + doctor.toFix = [{}]; + (await doctor.reportSuccess(doctor.toFix.length)).should.equal(false); + }); + }) + ); + + describe( + 'reportManualFixes', + withSandbox({}, (S) => { + let doctor = new Doctor(); + it('should ask for manual fixes to be applied', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + doctor.toFix = [ + { + error: 'Oh no this need to be manually fixed.', + check: new DoctorCheck(), + }, + { + error: 'Oh no this is an autofix.', + check: new DoctorCheck({autofix: true}), + }, + { + error: 'Oh no this also need to be manually fixed.', + check: new DoctorCheck(), + }, + { + error: 'Oh no this also need to be manually fixed.', + check: new DoctorCheck(), + }, + ]; + doctor.toFixOptionals = []; + for (let i = 0; i < doctor.toFix.length; i++) { + let m = S.sandbox.mock(doctor.toFix[i].check); + if (doctor.toFix[i].check.autofix) { + m.expects('fix').never(); + } else { + m.expects('fix') + .once() + .returns(B.resolve(`Manual fix for ${i} is do something.`)); + } + } + (await doctor.reportManualFixes(doctor.toFix, doctor.toFixOptionals)).should.equal(true); + S.verify(); + logStub.output.should.equal( + [ + 'info: ### Manual Fixes Needed ###', + 'info: The configuration cannot be automatically fixed, please do the following first:', + 'warn: ➜ Manual fix for 0 is do something.', + 'warn: ➜ Manual fix for 2 is do something.', + 'warn: ➜ Manual fix for 3 is do something.', + 'info: ', + 'info: ###', + 'info: ', + 'info: Bye! Run appium-doctor again when all manual fixes have been applied!', + 'info: ', + ].join('\n') + ); + }); + + it('should ask for manual fixes to be applied for optional', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + doctor.toFix = []; + doctor.toFixOptionals = [ + { + error: 'Oh no this need to be manually fixed.', + check: new DoctorCheck(), + }, + { + error: 'Oh no this also need to be manually fixed.', + check: new DoctorCheck(), + }, + ]; + for (let i = 0; i < doctor.toFixOptionals.length; i++) { + let m = S.sandbox.mock(doctor.toFixOptionals[i].check); + if (doctor.toFixOptionals[i].check.autofix) { + m.expects('fix').never(); + } else { + m.expects('fix') + .once() + .returns(B.resolve(`Manual fix for ${i} is do something.`)); + } + } + (await doctor.reportManualFixes(doctor.toFix, doctor.toFixOptionals)).should.equal(true); + S.verify(); + logStub.output.should.equal( + [ + 'info: ### Optional Manual Fixes ###', + 'info: The configuration can install optionally. Please do the following manually:', + 'warn: ➜ Manual fix for 0 is do something.', + 'warn: ➜ Manual fix for 1 is do something.', + 'info: ', + 'info: ###', + 'info: ', + 'info: Bye! Run appium-doctor again when all manual fixes have been applied!', + 'info: ', + ].join('\n') + ); + }); + + it('should ask for manual fixes to be applied for necessary and optional', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + doctor.toFix = [ + { + error: 'Oh no this need to be manually fixed.', + check: new DoctorCheck(), + }, + ]; + doctor.toFixOptionals = [ + { + error: 'Oh no this need to be manually fixed, but it is optional.', + check: new DoctorCheck(), + }, + ]; + + S.sandbox + .mock(doctor.toFix[0].check) + .expects('fix') + .once() + .returns(B.resolve(`Manual fix for 0 is do something.`)); + S.sandbox + .mock(doctor.toFixOptionals[0].check) + .expects('fix') + .once() + .returns(B.resolve(`Manual fix for 0 is do something.`)); + + (await doctor.reportManualFixes(doctor.toFix, doctor.toFixOptionals)).should.equal(true); + S.verify(); + logStub.output.should.equal( + [ + 'info: ### Manual Fixes Needed ###', + 'info: The configuration cannot be automatically fixed, please do the following first:', + 'warn: ➜ Manual fix for 0 is do something.', + 'info: ', + 'info: ### Optional Manual Fixes ###', + 'info: The configuration can install optionally. Please do the following manually:', + 'warn: ➜ Manual fix for 0 is do something.', + 'info: ', + 'info: ###', + 'info: ', + 'info: Bye! Run appium-doctor again when all manual fixes have been applied!', + 'info: ', + ].join('\n') + ); + }); + + it('should return false when there is no manual fix', async function () { + doctor.toFix = [{error: 'Oh no!', check: new DoctorCheck({autofix: true})}]; + (await doctor.reportManualFixes()).should.equal(false); + }); + }) + ); + + describe( + 'runAutoFix', + withSandbox({}, (S) => { + let doctor = new Doctor(); + let fix = { + error: 'Something wrong!', + check: { + fix() {}, + diagnose() {}, + }, + }; + + it('fix - success', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + S.mocks.check = S.sandbox.mock(fix.check); + S.mocks.check.expects('fix').once(); + S.mocks.check + .expects('diagnose') + .once() + .returns( + B.resolve({ + ok: true, + optional: false, + message: 'It worked', + }) + ); + await doctor.runAutoFix(fix); + S.verify(); + logStub.output.should.equal( + [ + 'info: ### Fixing: Something wrong! ###', + 'info: Checking if this was fixed:', + 'info: ✔ It worked', + 'info: ### Fix was successfully applied ###', + ].join('\n') + ); + }); + + it('fix - skipped', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + S.mocks.check = S.sandbox.mock(fix.check); + S.mocks.check.expects('fix').once().throws(new FixSkippedError()); + await doctor.runAutoFix(fix); + S.verify(); + logStub.output.should.equal( + ['info: ### Fixing: Something wrong! ###', 'info: ### Skipped fix ###'].join('\n') + ); + }); + + it('fix - crash', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + S.mocks.check = S.sandbox.mock(fix.check); + S.mocks.check.expects('fix').once().throws(new Error('Oh No!')); + await doctor.runAutoFix(fix); + S.verify(); + logStub.output.should.equal( + [ + 'info: ### Fixing: Something wrong! ###', + 'warn: Error: Oh No!', + 'info: ### Fix did not succeed ###', + ].join('\n') + ); + }); + + it("fix - didn't fix", async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + S.mocks.check = S.sandbox.mock(fix.check); + S.mocks.check.expects('fix').once(); + S.mocks.check + .expects('diagnose') + .once() + .returns( + B.resolve({ + ok: false, + message: 'Still Weird!', + }) + ); + await doctor.runAutoFix(fix); + S.verify(); + logStub.output.should.equal( + [ + 'info: ### Fixing: Something wrong! ###', + 'info: Checking if this was fixed:', + 'info: ✖ Still Weird!', + 'info: ### Fix was applied but issue remains ###', + ].join('\n') + ); + }); + }) + ); + + describe( + 'runAutoFixes', + withSandbox({}, (S) => { + let doctor = new Doctor(); + it('success', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + doctor.toFix = [ + {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, + {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, + {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, + ]; + S.sandbox.stub(doctor, 'runAutoFix').callsFake((f) => { + log.info('Autofix log go there.'); f.fixed = true; - } else { - log.warn('failed, Autofix log go there.'); - } - succeed = !succeed; + }); + await doctor.runAutoFixes(); + doctor.runAutoFix.calledThrice.should.be.ok; + logStub.output.should.equal( + [ + 'info: Autofix log go there.', + 'info: ', + 'info: Autofix log go there.', + 'info: ', + 'info: Autofix log go there.', + 'info: ', + 'info: Bye! All issues have been fixed!', + 'info: ', + ].join('\n') + ); }); - await doctor.runAutoFixes(); - doctor.runAutoFix.calledThrice.should.be.ok; - logStub.output.should.equal([ - 'warn: failed, Autofix log go there.', - 'info: ', - 'info: succeeded, Autofix log go there.', - 'info: ', - 'warn: failed, Autofix log go there.', - 'info: ', - 'info: Bye! A few issues remain, fix manually and/or rerun appium-doctor!', - 'info: ', - ].join('\n')); - }); - })); - describe('run', withSandbox({}, (S) => { - let doctor = new Doctor(); - it('should work', async function () { - try { - let doctor = new Doctor(); + it('failure', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + doctor.toFix = [ + {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, + {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, + {error: 'Oh no.', check: new DoctorCheck({autofix: true})}, + ]; + let succeed = false; + S.sandbox.stub(doctor, 'runAutoFix').callsFake((f) => { + if (succeed) { + log.info('succeeded, Autofix log go there.'); + f.fixed = true; + } else { + log.warn('failed, Autofix log go there.'); + } + succeed = !succeed; + }); + await doctor.runAutoFixes(); + doctor.runAutoFix.calledThrice.should.be.ok; + logStub.output.should.equal( + [ + 'warn: failed, Autofix log go there.', + 'info: ', + 'info: succeeded, Autofix log go there.', + 'info: ', + 'warn: failed, Autofix log go there.', + 'info: ', + 'info: Bye! A few issues remain, fix manually and/or rerun appium-doctor!', + 'info: ', + ].join('\n') + ); + }); + }) + ); + + describe( + 'run', + withSandbox({}, (S) => { + let doctor = new Doctor(); + it('should work', async function () { + try { + let doctor = new Doctor(); + await doctor.run(); + } catch (err) {} + }); + it('should report success', async function () { + S.mocks.doctor = S.sandbox.mock(doctor); + S.mocks.doctor.expects('diagnose').once(); + S.mocks.doctor.expects('reportSuccess').once().returns(true); + S.mocks.doctor.expects('reportManualFixes').never(); + S.mocks.doctor.expects('runAutoFixes').never(); await doctor.run(); - } catch (err) { - } - }); - it('should report success', async function () { - S.mocks.doctor = S.sandbox.mock(doctor); - S.mocks.doctor.expects('diagnose').once(); - S.mocks.doctor.expects('reportSuccess').once().returns(true); - S.mocks.doctor.expects('reportManualFixes').never(); - S.mocks.doctor.expects('runAutoFixes').never(); - await doctor.run(); - S.verify(); - }); - it('should report manual fixes', async function () { - S.mocks.doctor = S.sandbox.mock(doctor); - S.mocks.doctor.expects('diagnose').once(); - S.mocks.doctor.expects('reportSuccess').once().returns(false); - S.mocks.doctor.expects('reportManualFixes').once().returns(true); - S.mocks.doctor.expects('runAutoFixes').never(); - await doctor.run(); - S.verify(); - }); - it('should run autofixes', async function () { - S.mocks.doctor = S.sandbox.mock(doctor); - S.mocks.doctor.expects('diagnose').once(); - S.mocks.doctor.expects('reportSuccess').once().returns(false); - S.mocks.doctor.expects('reportManualFixes').once().returns(false); - S.mocks.doctor.expects('runAutoFixes').once(); - await doctor.run(); - S.verify(); - }); - })); + S.verify(); + }); + it('should report manual fixes', async function () { + S.mocks.doctor = S.sandbox.mock(doctor); + S.mocks.doctor.expects('diagnose').once(); + S.mocks.doctor.expects('reportSuccess').once().returns(false); + S.mocks.doctor.expects('reportManualFixes').once().returns(true); + S.mocks.doctor.expects('runAutoFixes').never(); + await doctor.run(); + S.verify(); + }); + it('should run autofixes', async function () { + S.mocks.doctor = S.sandbox.mock(doctor); + S.mocks.doctor.expects('diagnose').once(); + S.mocks.doctor.expects('reportSuccess').once().returns(false); + S.mocks.doctor.expects('reportManualFixes').once().returns(false); + S.mocks.doctor.expects('runAutoFixes').once(); + await doctor.run(); + S.verify(); + }); + }) + ); }); diff --git a/packages/doctor/test/unit/factory.spec.js b/packages/doctor/test/unit/factory.spec.js index 0b8b2dc9f..16b3921e9 100644 --- a/packages/doctor/test/unit/factory.spec.js +++ b/packages/doctor/test/unit/factory.spec.js @@ -1,15 +1,14 @@ import newDoctor from '../../lib/factory'; - describe('factory', function () { - function getTest (config) { - return function runTest () { + function getTest(config) { + return function runTest() { let doctor = newDoctor(config); doctor.should.exist; doctor.checks.should.have.length.above(0); }; } - for (let config of [{'ios': true}, {'android': true}, {'dev': true}]) { + for (let config of [{ios: true}, {android: true}, {dev: true}]) { it(`should work for ${config}`, getTest(config)); } }); diff --git a/packages/doctor/test/unit/general.spec.js b/packages/doctor/test/unit/general.spec.js index 518567e9d..828783adc 100644 --- a/packages/doctor/test/unit/general.spec.js +++ b/packages/doctor/test/unit/general.spec.js @@ -1,122 +1,158 @@ -import { NodeBinaryCheck, NodeVersionCheck, - OptionalFfmpegCommandCheck, OptionalMjpegConsumerCommandCheck } from '../../lib/general'; +import { + NodeBinaryCheck, + NodeVersionCheck, + OptionalFfmpegCommandCheck, + OptionalMjpegConsumerCommandCheck, +} from '../../lib/general'; import * as tp from 'teen_process'; import * as utils from '../../lib/utils'; import NodeDetector from '../../lib/node-detector'; -import { withMocks } from '@appium/test-support'; +import {withMocks} from '@appium/test-support'; import B from 'bluebird'; -import { removeColors } from './helper'; - +import {removeColors} from './helper'; describe('general', function () { - describe('NodeBinaryCheck', withMocks({NodeDetector}, (mocks) => { - let check = new NodeBinaryCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - mocks.NodeDetector.expects('detect').once().returns(B.resolve('/a/b/c/d')); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'The Node.js binary was found at: /a/b/c/d' + describe( + 'NodeBinaryCheck', + withMocks({NodeDetector}, (mocks) => { + let check = new NodeBinaryCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('diagnose - failure', async function () { - mocks.NodeDetector.expects('detect').once().returns(B.resolve(null)); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'The Node.js binary was NOT found!' + it('diagnose - success', async function () { + mocks.NodeDetector.expects('detect').once().returns(B.resolve('/a/b/c/d')); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: 'The Node.js binary was found at: /a/b/c/d', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal('Manually setup Node.js.'); - }); - })); + it('diagnose - failure', async function () { + mocks.NodeDetector.expects('detect').once().returns(B.resolve(null)); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'The Node.js binary was NOT found!', + }); + mocks.verify(); + }); + it('fix', async function () { + removeColors(await check.fix()).should.equal('Manually setup Node.js.'); + }); + }) + ); - describe('NodeVersionCheck', withMocks({NodeDetector, tp}, (mocks) => { - let check = new NodeVersionCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - mocks.NodeDetector.expects('detect').once().returns(B.resolve('/a/b/c/d')); - mocks.tp.expects('exec').once().returns(B.resolve({stdout: 'v10.0.0', stderr: ''})); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'Node version is 10.0.0' + describe( + 'NodeVersionCheck', + withMocks({NodeDetector, tp}, (mocks) => { + let check = new NodeVersionCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('diagnose - failure - insufficient version', async function () { - mocks.NodeDetector.expects('detect').once().returns(B.resolve('/a/b/c/d')); - mocks.tp.expects('exec').once().returns(B.resolve({stdout: 'v0.12.18', stderr: ''})); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'Node version should be at least 10.0.0!' + it('diagnose - success', async function () { + mocks.NodeDetector.expects('detect').once().returns(B.resolve('/a/b/c/d')); + mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: 'v10.0.0', stderr: ''})); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: 'Node version is 10.0.0', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('diagnose - failure - bad output', async function () { - mocks.NodeDetector.expects('detect').once().returns(B.resolve('/a/b/c/d')); - mocks.tp.expects('exec').once().returns(B.resolve({stdout: 'blahblahblah', stderr: ''})); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: `Unable to find node version (version = 'blahblahblah')` + it('diagnose - failure - insufficient version', async function () { + mocks.NodeDetector.expects('detect').once().returns(B.resolve('/a/b/c/d')); + mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: 'v0.12.18', stderr: ''})); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'Node version should be at least 10.0.0!', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal('Manually upgrade Node.js.'); - }); - })); + it('diagnose - failure - bad output', async function () { + mocks.NodeDetector.expects('detect').once().returns(B.resolve('/a/b/c/d')); + mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: 'blahblahblah', stderr: ''})); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: `Unable to find node version (version = 'blahblahblah')`, + }); + mocks.verify(); + }); + it('fix', async function () { + removeColors(await check.fix()).should.equal('Manually upgrade Node.js.'); + }); + }) + ); - describe('OptionalFfmpegCommandCheck', withMocks({tp, utils}, (mocks) => { - let check = new OptionalFfmpegCommandCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - mocks.tp.expects('exec').once().returns({stdout: `ffmpeg version 4.1 Copyright (c) 2000-2018 the FFmpeg developers + describe( + 'OptionalFfmpegCommandCheck', + withMocks({tp, utils}, (mocks) => { + let check = new OptionalFfmpegCommandCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; + }); + it('diagnose - success', async function () { + mocks.tp + .expects('exec') + .once() + .returns({ + stdout: `ffmpeg version 4.1 Copyright (c) 2000-2018 the FFmpeg developers built with Apple LLVM version 10.0.0 (clang-1000.11.45.5) configuration: --prefix=/usr/local/Cellar/ffmpeg/4.1_1 --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gpl --enable-libmp3lame --enable-libopus --enable-libsnappy --enable-libtheora --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265 --enable-libxvid --enable-lzma --enable-opencl --enable-videotoolbox libavutil 56. 22.100 / 56. 22.100 - libpostproc 55. 3.100 / 55. 3.100`, stderr: ''}); - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/ffmpeg'); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: true, - message: 'ffmpeg is installed at: path/to/ffmpeg. ffmpeg version 4.1 Copyright (c) 2000-2018 the FFmpeg developers' + libpostproc 55. 3.100 / 55. 3.100`, + stderr: '', + }); + mocks.utils.expects('resolveExecutablePath').once().returns('path/to/ffmpeg'); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: true, + message: + 'ffmpeg is installed at: path/to/ffmpeg. ffmpeg version 4.1 Copyright (c) 2000-2018 the FFmpeg developers', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('diagnose - failure', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns(false); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: true, - message: 'ffmpeg cannot be found' + it('diagnose - failure', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns(false); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: true, + message: 'ffmpeg cannot be found', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal('ffmpeg is needed to record screen features. Please read https://www.ffmpeg.org/ to install it'); - }); - })); + it('fix', async function () { + removeColors(await check.fix()).should.equal( + 'ffmpeg is needed to record screen features. Please read https://www.ffmpeg.org/ to install it' + ); + }); + }) + ); - describe('OptionalMjpegConsumerCommandCheck', withMocks({tp}, (mocks) => { - let check = new OptionalMjpegConsumerCommandCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - mocks.tp.expects('exec').once().returns({stdout: ` + describe( + 'OptionalMjpegConsumerCommandCheck', + withMocks({tp}, (mocks) => { + let check = new OptionalMjpegConsumerCommandCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; + }); + it('diagnose - success', async function () { + mocks.tp + .expects('exec') + .once() + .returns({ + stdout: ` { "dependencies": { "mjpeg-consumer": { @@ -127,26 +163,31 @@ describe('general', function () { }, "path": "/path/to/node/node/v11.4.0/lib" } - `, stderr: ''}); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: true, - message: 'mjpeg-consumer is installed at: /path/to/node/node/v11.4.0/lib. Installed version is: 1.1.0' + `, + stderr: '', + }); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: true, + message: + 'mjpeg-consumer is installed at: /path/to/node/node/v11.4.0/lib. Installed version is: 1.1.0', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('diagnose - failure', async function () { - mocks.tp.expects('exec').once().returns({stdout: '{}', stderr: ''}); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: true, - message: 'mjpeg-consumer cannot be found.' + it('diagnose - failure', async function () { + mocks.tp.expects('exec').once().returns({stdout: '{}', stderr: ''}); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: true, + message: 'mjpeg-consumer cannot be found.', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should. - equal("mjpeg-consumer module is required to use MJPEG-over-HTTP features. Please install it with 'npm i -g mjpeg-consumer'."); - }); - })); + it('fix', async function () { + removeColors(await check.fix()).should.equal( + "mjpeg-consumer module is required to use MJPEG-over-HTTP features. Please install it with 'npm i -g mjpeg-consumer'." + ); + }); + }) + ); }); diff --git a/packages/doctor/test/unit/helper.js b/packages/doctor/test/unit/helper.js index 703546b06..ea245f32d 100644 --- a/packages/doctor/test/unit/helper.js +++ b/packages/doctor/test/unit/helper.js @@ -4,9 +4,13 @@ * @param {string} text * @returns {string} The text which has no ANSI colors/styles */ -function removeColors (text) { +function removeColors(text) { // https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings - return text.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''); // eslint-disable-line no-control-regex + return text.replace( + // eslint-disable-next-line no-control-regex + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, + '' + ); } -export { removeColors }; +export {removeColors}; diff --git a/packages/doctor/test/unit/index.spec.js b/packages/doctor/test/unit/index.spec.js index dcb17ae09..5341a1849 100644 --- a/packages/doctor/test/unit/index.spec.js +++ b/packages/doctor/test/unit/index.spec.js @@ -1,5 +1,4 @@ -import { newDoctor, Doctor, DoctorCheck } from '../../lib'; - +import {newDoctor, Doctor, DoctorCheck} from '../../lib'; describe('index', function () { it('should work', function () { diff --git a/packages/doctor/test/unit/ios.spec.js b/packages/doctor/test/unit/ios.spec.js index 9adfa3429..87cd262a2 100644 --- a/packages/doctor/test/unit/ios.spec.js +++ b/packages/doctor/test/unit/ios.spec.js @@ -1,306 +1,388 @@ import { - fixes, XcodeCheck, XcodeCmdLineToolsCheck, DevToolsSecurityCheck, - OptionalApplesimutilsCommandCheck, OptionalIdbCommandCheck, OptionalIOSDeployCommandCheck, - OptionalLyftCommandCheck + fixes, + XcodeCheck, + XcodeCmdLineToolsCheck, + DevToolsSecurityCheck, + OptionalApplesimutilsCommandCheck, + OptionalIdbCommandCheck, + OptionalIOSDeployCommandCheck, + OptionalLyftCommandCheck, } from '../../lib/ios'; -import { fs, system } from '@appium/support'; +import {fs, system} from '@appium/support'; import * as utils from '../../lib/utils'; import * as tp from 'teen_process'; import * as prompter from '../../lib/prompt'; import FixSkippedError from '../../lib/doctor'; import log from '../../lib/logger'; import B from 'bluebird'; -import { withMocks, withSandbox, stubLog } from '@appium/test-support'; +import {withMocks, withSandbox, stubLog} from '@appium/test-support'; import {removeColors} from './helper'; - describe('ios', function () { - describe('XcodeCheck', withMocks({tp, fs}, (mocks) => { - let check = new XcodeCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - // xcrun - mocks.tp.expects('exec').once().resolves( - {stdout: 'usage: simctl [--set ] [--profiles ] ...', stderr: ''}); - // xcode-select - mocks.tp.expects('exec').once().returns( - B.resolve({stdout: '/a/b/c/d\n', stderr: ''})); - mocks.fs.expects('exists').once().resolves(true); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'Xcode is installed at: /a/b/c/d' + describe( + 'XcodeCheck', + withMocks({tp, fs}, (mocks) => { + let check = new XcodeCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('diagnose - failure - xcode-select', async function () { - // xcrun - mocks.tp.expects('exec').once().returns( - B.resolve({stdout: 'usage: simctl [--set ] [--profiles ] ...', stderr: ''})); - // xcode-select - mocks.tp.expects('exec').once().rejects(new Error('Something wrong!')); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'Xcode is NOT installed!' + it('diagnose - success', async function () { + // xcrun + mocks.tp.expects('exec').once().resolves({ + stdout: 'usage: simctl [--set ] [--profiles ] ...', + stderr: '', + }); + // xcode-select + mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: '/a/b/c/d\n', stderr: ''})); + mocks.fs.expects('exists').once().resolves(true); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: 'Xcode is installed at: /a/b/c/d', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('diagnose - failure - path not exists', async function () { - // xcrun - mocks.tp.expects('exec').once().returns( - B.resolve({stdout: 'usage: simctl [--set ] [--profiles ] ...', stderr: ''})); - // xcode-select - mocks.tp.expects('exec').once().returns( - B.resolve({stdout: '/a/b/c/d\n', stderr: ''})); - mocks.fs.expects('exists').once().resolves(false); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'Xcode cannot be found at \'/a/b/c/d\'!' + it('diagnose - failure - xcode-select', async function () { + // xcrun + mocks.tp + .expects('exec') + .once() + .returns( + B.resolve({ + stdout: 'usage: simctl [--set ] [--profiles ] ...', + stderr: '', + }) + ); + // xcode-select + mocks.tp.expects('exec').once().rejects(new Error('Something wrong!')); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'Xcode is NOT installed!', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('diagnose - failure - xcrun does not work', async function () { - // xcrun - mocks.tp.expects('exec').once().rejects(new Error('xcrun: error: unable to find utility "simctl", not a developer tool or in PATH')); - // no xcode-select - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'Error running xcrun simctl' + it('diagnose - failure - path not exists', async function () { + // xcrun + mocks.tp + .expects('exec') + .once() + .returns( + B.resolve({ + stdout: 'usage: simctl [--set ] [--profiles ] ...', + stderr: '', + }) + ); + // xcode-select + mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: '/a/b/c/d\n', stderr: ''})); + mocks.fs.expects('exists').once().resolves(false); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: "Xcode cannot be found at '/a/b/c/d'!", + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal("Manually install Xcode, and make sure 'xcode-select -p' command shows proper path like '/Applications/Xcode.app/Contents/Developer'"); - }); - })); - describe('XcodeCmdLineToolsCheck', withSandbox({mocks: {tp, utils, prompter, system}}, (S) => { - let check = new XcodeCmdLineToolsCheck(); - it('autofix', function () { - check.autofix.should.be.ok; - }); - it('diagnose - success', async function () { - S.mocks.tp.expects('exec').once().returns( - B.resolve({stdout: '/Applications/Xcode.app/Contents/Developer\n', stderr: ''})); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'Xcode Command Line Tools are installed in: /Applications/Xcode.app/Contents/Developer' + it('diagnose - failure - xcrun does not work', async function () { + // xcrun + mocks.tp + .expects('exec') + .once() + .rejects( + new Error( + 'xcrun: error: unable to find utility "simctl", not a developer tool or in PATH' + ) + ); + // no xcode-select + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'Error running xcrun simctl', + }); + mocks.verify(); }); - S.verify(); - }); - it('diagnose - failure - pkgutil crash', async function () { - S.mocks.tp.expects('exec').once().throws(new Error('Something wrong!')); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'Xcode Command Line Tools are NOT installed!' + it('fix', async function () { + removeColors(await check.fix()).should.equal( + "Manually install Xcode, and make sure 'xcode-select -p' command shows proper path like '/Applications/Xcode.app/Contents/Developer'" + ); }); - S.verify(); - }); - it('diagnose - failure - xcode-select -p returns status 2', async function () { - S.mocks.tp.expects('exec').once().throws(new Error()); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'Xcode Command Line Tools are NOT installed!' + }) + ); + describe( + 'XcodeCmdLineToolsCheck', + withSandbox({mocks: {tp, utils, prompter, system}}, (S) => { + let check = new XcodeCmdLineToolsCheck(); + it('autofix', function () { + check.autofix.should.be.ok; }); - S.verify(); - }); - it('fix - yes', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - S.mocks.tp.expects('exec').once().returns( - B.resolve({stdout: '', stderr: ''})); - S.mocks.prompter.expects('fixIt').once().resolves('yes'); - await check.fix(); - S.verify(); - logStub.output.should.equal([ - 'info: The following command need be executed: xcode-select --install', - ].join('\n')); - }); - it('fix - no', async function () { - let logStub = stubLog(S.sandbox, log, {stripColors: true}); - S.mocks.tp.expects('exec').never(); - S.mocks.prompter.expects('fixIt').once().resolves('no'); - await check.fix().should.be.rejectedWith(FixSkippedError); - S.verify(); - logStub.output.should.equal([ - 'info: The following command need be executed: xcode-select --install', - 'info: Skipping you will need to install Xcode manually.' - ].join('\n')); - }); - })); - describe('DevToolsSecurityCheck', withMocks({fixes, tp}, (mocks) => { - let check = new DevToolsSecurityCheck(); - it('diagnose - success', async function () { - mocks.tp.expects('exec').once().returns( - B.resolve({stdout: '1234 enabled\n', stderr: ''})); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: false, - message: 'DevToolsSecurity is enabled.' + it('diagnose - success', async function () { + S.mocks.tp + .expects('exec') + .once() + .returns( + B.resolve({ + stdout: '/Applications/Xcode.app/Contents/Developer\n', + stderr: '', + }) + ); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: + 'Xcode Command Line Tools are installed in: /Applications/Xcode.app/Contents/Developer', + }); + S.verify(); }); - mocks.verify(); - }); - it('diagnose - failure - DevToolsSecurity crash', async function () { - mocks.tp.expects('exec').once().rejects(new Error('Something wrong!')); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'DevToolsSecurity is NOT enabled!' + it('diagnose - failure - pkgutil crash', async function () { + S.mocks.tp.expects('exec').once().throws(new Error('Something wrong!')); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'Xcode Command Line Tools are NOT installed!', + }); + S.verify(); }); - mocks.verify(); - }); - it('diagnose - failure - not enabled', async function () { - mocks.tp.expects('exec').once().returns( - B.resolve({stdout: '1234 abcd\n', stderr: ''})); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: false, - message: 'DevToolsSecurity is NOT enabled!' + it('diagnose - failure - xcode-select -p returns status 2', async function () { + S.mocks.tp.expects('exec').once().throws(new Error()); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'Xcode Command Line Tools are NOT installed!', + }); + S.verify(); }); - mocks.verify(); - }); - })); + it('fix - yes', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + S.mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: '', stderr: ''})); + S.mocks.prompter.expects('fixIt').once().resolves('yes'); + await check.fix(); + S.verify(); + logStub.output.should.equal( + ['info: The following command need be executed: xcode-select --install'].join('\n') + ); + }); + it('fix - no', async function () { + let logStub = stubLog(S.sandbox, log, {stripColors: true}); + S.mocks.tp.expects('exec').never(); + S.mocks.prompter.expects('fixIt').once().resolves('no'); + await check.fix().should.be.rejectedWith(FixSkippedError); + S.verify(); + logStub.output.should.equal( + [ + 'info: The following command need be executed: xcode-select --install', + 'info: Skipping you will need to install Xcode manually.', + ].join('\n') + ); + }); + }) + ); + describe( + 'DevToolsSecurityCheck', + withMocks({fixes, tp}, (mocks) => { + let check = new DevToolsSecurityCheck(); + it('diagnose - success', async function () { + mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: '1234 enabled\n', stderr: ''})); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: false, + message: 'DevToolsSecurity is enabled.', + }); + mocks.verify(); + }); + it('diagnose - failure - DevToolsSecurity crash', async function () { + mocks.tp.expects('exec').once().rejects(new Error('Something wrong!')); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'DevToolsSecurity is NOT enabled!', + }); + mocks.verify(); + }); + it('diagnose - failure - not enabled', async function () { + mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: '1234 abcd\n', stderr: ''})); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: false, + message: 'DevToolsSecurity is NOT enabled!', + }); + mocks.verify(); + }); + }) + ); - describe('OptionalLyftCommandCheck', withMocks({tp, utils}, (mocks) => { - let check = new OptionalLyftCommandCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/set-simulator-location'); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: true, - message: 'set-simulator-location is installed' + describe( + 'OptionalLyftCommandCheck', + withMocks({tp, utils}, (mocks) => { + let check = new OptionalLyftCommandCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('diagnose - failure', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns(false); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: true, - message: 'set-simulator-location is not installed' + it('diagnose - success', async function () { + mocks.utils + .expects('resolveExecutablePath') + .once() + .returns('path/to/set-simulator-location'); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: true, + message: 'set-simulator-location is installed', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal('set-simulator-location is needed to set location for Simulator. ' + - 'Please read https://github.com/lyft/set-simulator-location to install it'); - }); - })); + it('diagnose - failure', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns(false); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: true, + message: 'set-simulator-location is not installed', + }); + mocks.verify(); + }); + it('fix', async function () { + removeColors(await check.fix()).should.equal( + 'set-simulator-location is needed to set location for Simulator. ' + + 'Please read https://github.com/lyft/set-simulator-location to install it' + ); + }); + }) + ); - describe('OptionalIdbCommandCheck', withMocks({tp, utils}, (mocks) => { - let check = new OptionalIdbCommandCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/idb'); - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/idb_cpmpanion'); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: true, - message: 'idb and idb_companion are installed' + describe( + 'OptionalIdbCommandCheck', + withMocks({tp, utils}, (mocks) => { + let check = new OptionalIdbCommandCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('diagnose - failure because of no idb_companion and idb', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns(false); - mocks.utils.expects('resolveExecutablePath').once().returns(false); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: true, - message: 'idb and idb_companion are not installed' + it('diagnose - success', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns('path/to/idb'); + mocks.utils.expects('resolveExecutablePath').once().returns('path/to/idb_cpmpanion'); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: true, + message: 'idb and idb_companion are installed', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('diagnose - failure because of no idb_companion', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/idb'); - mocks.utils.expects('resolveExecutablePath').once().returns(false); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: true, - message: 'idb_companion is not installed' + it('diagnose - failure because of no idb_companion and idb', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns(false); + mocks.utils.expects('resolveExecutablePath').once().returns(false); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: true, + message: 'idb and idb_companion are not installed', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('diagnose - failure because of no idb', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns(false); - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/idb_cpmpanion'); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: true, - message: 'idb is not installed' + it('diagnose - failure because of no idb_companion', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns('path/to/idb'); + mocks.utils.expects('resolveExecutablePath').once().returns(false); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: true, + message: 'idb_companion is not installed', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal('Why idb is needed and how to install it: https://git.io/JnxQc'); - }); - })); + it('diagnose - failure because of no idb', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns(false); + mocks.utils.expects('resolveExecutablePath').once().returns('path/to/idb_cpmpanion'); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: true, + message: 'idb is not installed', + }); + mocks.verify(); + }); + it('fix', async function () { + removeColors(await check.fix()).should.equal( + 'Why idb is needed and how to install it: https://git.io/JnxQc' + ); + }); + }) + ); - describe('OptionalApplesimutilsCommandCheck', withMocks({tp, utils}, (mocks) => { - let check = new OptionalApplesimutilsCommandCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/applesimutils'); - mocks.tp.expects('exec').once().returns({stdout: 'vxx.xx.xx', stderr: ''}); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: true, - message: 'applesimutils is installed at: path/to/applesimutils. Installed versions are: vxx.xx.xx' + describe( + 'OptionalApplesimutilsCommandCheck', + withMocks({tp, utils}, (mocks) => { + let check = new OptionalApplesimutilsCommandCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('diagnose - failure', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns(false); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: true, - message: 'applesimutils cannot be found' + it('diagnose - success', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns('path/to/applesimutils'); + mocks.tp.expects('exec').once().returns({stdout: 'vxx.xx.xx', stderr: ''}); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: true, + message: + 'applesimutils is installed at: path/to/applesimutils. Installed versions are: vxx.xx.xx', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal('Why applesimutils is needed and how to install it: http://appium.io/docs/en/drivers/ios-xcuitest/'); - }); - })); + it('diagnose - failure', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns(false); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: true, + message: 'applesimutils cannot be found', + }); + mocks.verify(); + }); + it('fix', async function () { + removeColors(await check.fix()).should.equal( + 'Why applesimutils is needed and how to install it: http://appium.io/docs/en/drivers/ios-xcuitest/' + ); + }); + }) + ); - describe('OptionalIOSDeployCommandCheck', withMocks({tp, utils}, (mocks) => { - let check = new OptionalIOSDeployCommandCheck(); - it('autofix', function () { - check.autofix.should.not.be.ok; - }); - it('diagnose - success', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns('path/to/ios-deploy'); - mocks.tp.expects('exec').once().returns({stdout: '1.9.4', stderr: ''}); - (await check.diagnose()).should.deep.equal({ - ok: true, - optional: true, - message: 'ios-deploy is installed at: path/to/ios-deploy. Installed version is: 1.9.4' + describe( + 'OptionalIOSDeployCommandCheck', + withMocks({tp, utils}, (mocks) => { + let check = new OptionalIOSDeployCommandCheck(); + it('autofix', function () { + check.autofix.should.not.be.ok; }); - mocks.verify(); - }); - it('diagnose - failure', async function () { - mocks.utils.expects('resolveExecutablePath').once().returns(false); - (await check.diagnose()).should.deep.equal({ - ok: false, - optional: true, - message: 'ios-deploy cannot be found' + it('diagnose - success', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns('path/to/ios-deploy'); + mocks.tp.expects('exec').once().returns({stdout: '1.9.4', stderr: ''}); + (await check.diagnose()).should.deep.equal({ + ok: true, + optional: true, + message: 'ios-deploy is installed at: path/to/ios-deploy. Installed version is: 1.9.4', + }); + mocks.verify(); }); - mocks.verify(); - }); - it('fix', async function () { - removeColors(await check.fix()).should.equal('ios-deploy is used as a fallback command to install iOS applications to real device. Please read https://github.com/ios-control/ios-deploy/ to install it'); - }); - })); + it('diagnose - failure', async function () { + mocks.utils.expects('resolveExecutablePath').once().returns(false); + (await check.diagnose()).should.deep.equal({ + ok: false, + optional: true, + message: 'ios-deploy cannot be found', + }); + mocks.verify(); + }); + it('fix', async function () { + removeColors(await check.fix()).should.equal( + 'ios-deploy is used as a fallback command to install iOS applications to real device. Please read https://github.com/ios-control/ios-deploy/ to install it' + ); + }); + }) + ); }); diff --git a/packages/doctor/test/unit/node-detector.spec.js b/packages/doctor/test/unit/node-detector.spec.js index 8f0533649..4336465cb 100644 --- a/packages/doctor/test/unit/node-detector.spec.js +++ b/packages/doctor/test/unit/node-detector.spec.js @@ -1,119 +1,116 @@ -import { fs, system } from '@appium/support'; +import {fs, system} from '@appium/support'; import * as tp from 'teen_process'; import NodeDetector from '../../lib/node-detector'; import B from 'bluebird'; -import { withSandbox } from '@appium/test-support'; -import { EOL } from 'os'; - +import {withSandbox} from '@appium/test-support'; +import {EOL} from 'os'; let expect = chai.expect; -describe('NodeDetector', withSandbox({mocks: {fs, tp, system}}, (S) => { - it('retrieveInCommonPlaces - success', async function () { - S.mocks.fs.expects('exists').once().returns(B.resolve(true)); - (await NodeDetector.retrieveInCommonPlaces()) - .should.equal('/usr/local/bin/node'); - S.verify(); - }); +describe( + 'NodeDetector', + withSandbox({mocks: {fs, tp, system}}, (S) => { + it('retrieveInCommonPlaces - success', async function () { + S.mocks.fs.expects('exists').once().returns(B.resolve(true)); + (await NodeDetector.retrieveInCommonPlaces()).should.equal('/usr/local/bin/node'); + S.verify(); + }); - it('retrieveInCommonPlaces - failure', async function () { - S.mocks.fs.expects('exists').twice().returns(B.resolve(false)); - expect(await NodeDetector.retrieveInCommonPlaces()).to.be.a('null'); - S.verify(); - }); + it('retrieveInCommonPlaces - failure', async function () { + S.mocks.fs.expects('exists').twice().returns(B.resolve(false)); + expect(await NodeDetector.retrieveInCommonPlaces()).to.be.a('null'); + S.verify(); + }); - it('retrieveUsingAppleScript - success', async function () { - system.isMac = () => true; - S.mocks.tp.expects('exec').once().returns( - B.resolve({stdout: `/a/b/c/d/node${EOL}`, stderr: ''})); - S.mocks.fs.expects('exists').once().returns(B.resolve(true)); - (await NodeDetector.retrieveUsingAppleScript()).should.equal('/a/b/c/d/node'); - S.verify(); - }); - it('retrieveUsingAppleScript - failure - path not found ', async function () { - system.isMac = () => true; - S.mocks.tp.expects('exec').once().throws(Error()); - expect(await NodeDetector.retrieveUsingAppleScript()).to.be.a('null'); - S.verify(); - }); - it('retrieveUsingAppleScript - failure - path not exist', async function () { - system.isMac = () => true; - S.mocks.tp.expects('exec').once().returns( - B.resolve({stdout: '/a/b/c/d\n', stderr: ''})); - S.mocks.fs.expects('exists').once().returns(B.resolve(false)); - expect(await NodeDetector.retrieveUsingAppleScript()).to.be.a('null'); - }); + it('retrieveUsingAppleScript - success', async function () { + system.isMac = () => true; + S.mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: `/a/b/c/d/node${EOL}`, stderr: ''})); + S.mocks.fs.expects('exists').once().returns(B.resolve(true)); + (await NodeDetector.retrieveUsingAppleScript()).should.equal('/a/b/c/d/node'); + S.verify(); + }); + it('retrieveUsingAppleScript - failure - path not found ', async function () { + system.isMac = () => true; + S.mocks.tp.expects('exec').once().throws(Error()); + expect(await NodeDetector.retrieveUsingAppleScript()).to.be.a('null'); + S.verify(); + }); + it('retrieveUsingAppleScript - failure - path not exist', async function () { + system.isMac = () => true; + S.mocks.tp + .expects('exec') + .once() + .returns(B.resolve({stdout: '/a/b/c/d\n', stderr: ''})); + S.mocks.fs.expects('exists').once().returns(B.resolve(false)); + expect(await NodeDetector.retrieveUsingAppleScript()).to.be.a('null'); + }); - it('retrieveUsingSystemCall - success - where returns multiple lines ', async function () { - S.mocks.fs.expects('which').once().returns(B.resolve('/a/b/node.exe')); - S.mocks.fs.expects('exists').once().returns(B.resolve(true)); - (await NodeDetector.retrieveUsingSystemCall()) - .should.equal('/a/b/node.exe'); - S.verify(); - }); - it('retrieveUsingSystemCall - success', async function () { - S.mocks.fs.expects('which').once().returns(B.resolve('/a/b/c/d/node')); - S.mocks.fs.expects('exists').once().returns(B.resolve(true)); - (await NodeDetector.retrieveUsingSystemCall()).should.equal('/a/b/c/d/node'); - S.verify(); - }); - it('retrieveUsingSystemCall - failure - path not found ', async function () { - S.mocks.fs.expects('which').once().throws(Error('not found: carthage')); - expect(await NodeDetector.retrieveUsingSystemCall()).to.be.a('null'); - S.verify(); - }); - it('retrieveUsingSystemCall - failure - path not exist', async function () { - S.mocks.fs.expects('which').once().returns( - B.resolve('/a/b/c/d')); - S.mocks.fs.expects('exists').once().returns(B.resolve(false)); - expect(await NodeDetector.retrieveUsingSystemCall()).to.be.a('null'); - }); + it('retrieveUsingSystemCall - success - where returns multiple lines ', async function () { + S.mocks.fs.expects('which').once().returns(B.resolve('/a/b/node.exe')); + S.mocks.fs.expects('exists').once().returns(B.resolve(true)); + (await NodeDetector.retrieveUsingSystemCall()).should.equal('/a/b/node.exe'); + S.verify(); + }); + it('retrieveUsingSystemCall - success', async function () { + S.mocks.fs.expects('which').once().returns(B.resolve('/a/b/c/d/node')); + S.mocks.fs.expects('exists').once().returns(B.resolve(true)); + (await NodeDetector.retrieveUsingSystemCall()).should.equal('/a/b/c/d/node'); + S.verify(); + }); + it('retrieveUsingSystemCall - failure - path not found ', async function () { + S.mocks.fs.expects('which').once().throws(Error('not found: carthage')); + expect(await NodeDetector.retrieveUsingSystemCall()).to.be.a('null'); + S.verify(); + }); + it('retrieveUsingSystemCall - failure - path not exist', async function () { + S.mocks.fs.expects('which').once().returns(B.resolve('/a/b/c/d')); + S.mocks.fs.expects('exists').once().returns(B.resolve(false)); + expect(await NodeDetector.retrieveUsingSystemCall()).to.be.a('null'); + }); - it('retrieveUsingAppiumConfigFile - success', async function () { - S.mocks.fs.expects('exists').twice().returns(B.resolve(true)); - S.mocks.fs.expects('readFile').once().returns( - B.resolve('{"node_bin": "/a/b/c/d"}')); - (await NodeDetector.retrieveUsingAppiumConfigFile()) - .should.equal('/a/b/c/d'); - S.verify(); - }); + it('retrieveUsingAppiumConfigFile - success', async function () { + S.mocks.fs.expects('exists').twice().returns(B.resolve(true)); + S.mocks.fs.expects('readFile').once().returns(B.resolve('{"node_bin": "/a/b/c/d"}')); + (await NodeDetector.retrieveUsingAppiumConfigFile()).should.equal('/a/b/c/d'); + S.verify(); + }); - it('retrieveUsingAppiumConfigFile - failure - not json', async function () { - S.mocks.fs.expects('exists').once().returns(B.resolve(true)); - S.mocks.fs.expects('readFile').once().returns( - B.resolve('{node_bin: "/a/b/c/d"}')); - expect(await NodeDetector.retrieveUsingAppiumConfigFile()) - .to.be.a('null'); - S.verify(); - }); + it('retrieveUsingAppiumConfigFile - failure - not json', async function () { + S.mocks.fs.expects('exists').once().returns(B.resolve(true)); + S.mocks.fs.expects('readFile').once().returns(B.resolve('{node_bin: "/a/b/c/d"}')); + expect(await NodeDetector.retrieveUsingAppiumConfigFile()).to.be.a('null'); + S.verify(); + }); - it('retrieveUsingAppiumConfigFile - failure - path does not exist', async function () { - S.mocks.fs.expects('exists').once().returns(B.resolve(true)); - S.mocks.fs.expects('exists').once().returns(B.resolve(false)); - S.mocks.fs.expects('readFile').once().returns( - B.resolve('{"node_bin": "/a/b/c/d"}')); - expect(await NodeDetector.retrieveUsingAppiumConfigFile()) - .to.be.a('null'); - S.verify(); - }); + it('retrieveUsingAppiumConfigFile - failure - path does not exist', async function () { + S.mocks.fs.expects('exists').once().returns(B.resolve(true)); + S.mocks.fs.expects('exists').once().returns(B.resolve(false)); + S.mocks.fs.expects('readFile').once().returns(B.resolve('{"node_bin": "/a/b/c/d"}')); + expect(await NodeDetector.retrieveUsingAppiumConfigFile()).to.be.a('null'); + S.verify(); + }); - it('checkForNodeBinary - success', async function () { - S.mocks.NodeDetector = S.sandbox.mock(NodeDetector); - S.mocks.NodeDetector.expects('retrieveInCommonPlaces').once().returns(null); - S.mocks.NodeDetector.expects('retrieveUsingSystemCall').once().returns(null); - S.mocks.NodeDetector.expects('retrieveUsingAppleScript').returns('/a/b/c/d'); - S.mocks.NodeDetector.expects('retrieveUsingAppiumConfigFile').never(); - (await NodeDetector.detect()).should.equal('/a/b/c/d'); - S.verify(); - }); + it('checkForNodeBinary - success', async function () { + S.mocks.NodeDetector = S.sandbox.mock(NodeDetector); + S.mocks.NodeDetector.expects('retrieveInCommonPlaces').once().returns(null); + S.mocks.NodeDetector.expects('retrieveUsingSystemCall').once().returns(null); + S.mocks.NodeDetector.expects('retrieveUsingAppleScript').returns('/a/b/c/d'); + S.mocks.NodeDetector.expects('retrieveUsingAppiumConfigFile').never(); + (await NodeDetector.detect()).should.equal('/a/b/c/d'); + S.verify(); + }); - it('checkForNodeBinary - failure', async function () { - S.mocks.NodeDetector = S.sandbox.mock(NodeDetector); - S.mocks.NodeDetector.expects('retrieveInCommonPlaces').once().returns(null); - S.mocks.NodeDetector.expects('retrieveUsingSystemCall').once().returns(null); - S.mocks.NodeDetector.expects('retrieveUsingAppleScript').once().returns(null); - S.mocks.NodeDetector.expects('retrieveUsingAppiumConfigFile').once().returns(null); - expect(await NodeDetector.detect()).to.be.a('null'); - S.verify(); - }); -})); + it('checkForNodeBinary - failure', async function () { + S.mocks.NodeDetector = S.sandbox.mock(NodeDetector); + S.mocks.NodeDetector.expects('retrieveInCommonPlaces').once().returns(null); + S.mocks.NodeDetector.expects('retrieveUsingSystemCall').once().returns(null); + S.mocks.NodeDetector.expects('retrieveUsingAppleScript').once().returns(null); + S.mocks.NodeDetector.expects('retrieveUsingAppiumConfigFile').once().returns(null); + expect(await NodeDetector.detect()).to.be.a('null'); + S.verify(); + }); + }) +); diff --git a/packages/doctor/test/unit/prompt.spec.js b/packages/doctor/test/unit/prompt.spec.js index 51b40d6a3..a1475a0fb 100644 --- a/packages/doctor/test/unit/prompt.spec.js +++ b/packages/doctor/test/unit/prompt.spec.js @@ -1,39 +1,43 @@ -import { fixIt, clear } from '../../lib/prompt'; -import { inquirer } from '../../lib/utils'; -import { withMocks } from '@appium/test-support'; +import {fixIt, clear} from '../../lib/prompt'; +import {inquirer} from '../../lib/utils'; +import {withMocks} from '@appium/test-support'; import B from 'bluebird'; +describe( + 'prompt', + withMocks({inquirer}, (mocks) => { + it('fixit - yes', async function () { + clear(); + mocks.inquirer + .expects('prompt') + .once() + .returns(B.resolve({confirmation: 'yes'})); + (await fixIt()).should.equal('yes'); + mocks.verify(); + }); + it('fixit always ', async function () { + clear(); + mocks.inquirer + .expects('prompt') + .once() + .returns(B.resolve({confirmation: 'always'})); + (await fixIt()).should.equal('yes'); + (await fixIt()).should.equal('yes'); + (await fixIt()).should.equal('yes'); + mocks.verify(); + }); -describe('prompt', withMocks({inquirer}, (mocks) => { - - it('fixit - yes', async function () { - clear(); - mocks.inquirer.expects('prompt').once().returns(B.resolve( - { confirmation: 'yes' })); - (await fixIt()).should.equal('yes'); - mocks.verify(); - }); - - it('fixit always ', async function () { - clear(); - mocks.inquirer.expects('prompt').once().returns(B.resolve( - { confirmation: 'always' })); - (await fixIt()).should.equal('yes'); - (await fixIt()).should.equal('yes'); - (await fixIt()).should.equal('yes'); - mocks.verify(); - }); - - it('fixit never ', async function () { - clear(); - mocks.inquirer.expects('prompt').once().returns(B.resolve( - { confirmation: 'never' })); - (await fixIt()).should.equal('no'); - (await fixIt()).should.equal('no'); - (await fixIt()).should.equal('no'); - mocks.verify(); - }); - - -})); + it('fixit never ', async function () { + clear(); + mocks.inquirer + .expects('prompt') + .once() + .returns(B.resolve({confirmation: 'never'})); + (await fixIt()).should.equal('no'); + (await fixIt()).should.equal('no'); + (await fixIt()).should.equal('no'); + mocks.verify(); + }); + }) +); diff --git a/packages/doctor/test/unit/util.spec.js b/packages/doctor/test/unit/util.spec.js index be9118fe8..645281329 100644 --- a/packages/doctor/test/unit/util.spec.js +++ b/packages/doctor/test/unit/util.spec.js @@ -1,29 +1,26 @@ -import { configureBinaryLog, resetLog } from '../../lib/utils'; -import { fs } from '@appium/support'; +import {configureBinaryLog, resetLog} from '../../lib/utils'; +import {fs} from '@appium/support'; import path from 'path'; -import { Doctor } from '../../lib/doctor'; - +import {Doctor} from '../../lib/doctor'; describe('utils', function () { - it('fs.readFile', async function () { - (await fs.readFile(path.resolve(__dirname, 'fixtures', - 'wow.txt'), 'utf8')).should.include('WOW'); + (await fs.readFile(path.resolve(__dirname, 'fixtures', 'wow.txt'), 'utf8')).should.include( + 'WOW' + ); }); it('fs.exists', async function () { - (await fs.exists(path.resolve(__dirname, 'fixtures', - 'wow.txt'))).should.be.ok; - (await fs.exists(path.resolve(__dirname, 'fixtures', - 'notwow.txt'))).should.not.be.ok; + (await fs.exists(path.resolve(__dirname, 'fixtures', 'wow.txt'))).should.be.ok; + (await fs.exists(path.resolve(__dirname, 'fixtures', 'notwow.txt'))).should.not.be.ok; }); it('Should handle logs through onLogMessage callback', async function () { - function onLogMessage (level, prefix, msg) { + function onLogMessage(level, prefix, msg) { `${level} ${prefix} ${msg}`.should.include('AppiumDoctor'); } - configureBinaryLog({ onLogMessage }); + configureBinaryLog({onLogMessage}); let doctor = new Doctor(); try { await doctor.run(); @@ -31,5 +28,4 @@ describe('utils', function () { resetLog(); } }); - }); diff --git a/packages/docutils/lib/index.js b/packages/docutils/lib/index.js index 6d99d7bff..2b0709b9b 100644 --- a/packages/docutils/lib/index.js +++ b/packages/docutils/lib/index.js @@ -2,4 +2,4 @@ import baseConfig from './jsdoc-config-base'; export * from './mkdocs'; export * from './mike'; -export { baseConfig }; +export {baseConfig}; diff --git a/packages/docutils/lib/jsdoc-config-base.js b/packages/docutils/lib/jsdoc-config-base.js index e6e3a30f4..0a982ac0c 100644 --- a/packages/docutils/lib/jsdoc-config-base.js +++ b/packages/docutils/lib/jsdoc-config-base.js @@ -29,12 +29,12 @@ const baseConfig = { 'Events', 'Namespaces', 'Mixins', - 'Interfaces' + 'Interfaces', ], navLevel: 5, search: true, collapse: false, - } + }, }; export default baseConfig; diff --git a/packages/docutils/lib/logger.js b/packages/docutils/lib/logger.js index 72bfa8dfd..5857fb267 100644 --- a/packages/docutils/lib/logger.js +++ b/packages/docutils/lib/logger.js @@ -1,3 +1,3 @@ -import { logger } from '@appium/support'; +import {logger} from '@appium/support'; export default logger.getLogger('Docutils'); diff --git a/packages/docutils/lib/mike.js b/packages/docutils/lib/mike.js index 9068224ff..c520db7b2 100644 --- a/packages/docutils/lib/mike.js +++ b/packages/docutils/lib/mike.js @@ -1,4 +1,4 @@ -import { exec } from 'teen_process'; +import {exec} from 'teen_process'; import log from './logger'; const DEFAULT_REMOTE = 'origin'; @@ -12,7 +12,7 @@ export class Mike { /** @type string */ configFile; /** @type boolean */ _mikeVerified = false; - constructor (/** @type MikeOpts */opts) { + constructor(/** @type MikeOpts */ opts) { this.remote = opts.remote || DEFAULT_REMOTE; this.branch = opts.branch || DEFAULT_BRANCH; this.prefix = opts.prefix; @@ -24,7 +24,7 @@ export class Mike { * * @throws Error */ - async verifyMike () { + async verifyMike() { if (this._mikeVerified) { return; } @@ -48,13 +48,18 @@ export class Mike { * * @returns string[] */ - getMikeArgs (cmdName, cmdArgs) { + getMikeArgs(cmdName, cmdArgs) { return [ - cmdName, ...cmdArgs, - '--config-file', this.configFile, - '--remote', this.remote, - '--branch', this.branch, - '--prefix', this.prefix, + cmdName, + ...cmdArgs, + '--config-file', + this.configFile, + '--remote', + this.remote, + '--branch', + this.branch, + '--prefix', + this.prefix, ]; } @@ -67,7 +72,7 @@ export class Mike { * * @returns Promise> */ - async exec (mikeCmd, mikeArgs = [], verify = true) { + async exec(mikeCmd, mikeArgs = [], verify = true) { if (verify) { await this.verifyMike(); } @@ -81,9 +86,12 @@ export class Mike { * * @returns string[] */ - async list () { + async list() { const {stdout} = await this.exec('list'); - return stdout.split('\n').map((s) => s.trim()).filter(Boolean); + return stdout + .split('\n') + .map((s) => s.trim()) + .filter(Boolean); } /** @@ -91,7 +99,7 @@ export class Mike { * * @param {string} alias - the version or alias */ - async setDefault (alias) { + async setDefault(alias) { await this.exec('set-default', [alias]); } @@ -100,7 +108,7 @@ export class Mike { * * @param {MikeDeployOpts} opts - the deploy options */ - async deploy (opts) { + async deploy(opts) { const args = [opts.version]; if (opts.alias) { args.push(opts.alias, '--update-aliases'); @@ -116,7 +124,6 @@ export class Mike { } await this.exec('deploy', args); } - } /** diff --git a/packages/docutils/lib/mkdocs.js b/packages/docutils/lib/mkdocs.js index 4060286a7..02a2b7843 100644 --- a/packages/docutils/lib/mkdocs.js +++ b/packages/docutils/lib/mkdocs.js @@ -1,19 +1,21 @@ -import { exec, SubProcess } from 'teen_process'; +import {exec, SubProcess} from 'teen_process'; const MKDOCS_VER_STR = 'version 1.'; /** * Run a version check on the system-installed mkdocs to make sure it is set up */ -export async function verifyMkdocs () { +export async function verifyMkdocs() { try { const {stdout} = await exec('mkdocs', ['-V']); if (!stdout.includes(MKDOCS_VER_STR)) { throw new Error('version mismatch'); } } catch (err) { - throw new Error(`Could not verify mkdocs 1.x is available. Make sure it's installed ` + - `and on your path. Specific error: ${err}`); + throw new Error( + `Could not verify mkdocs 1.x is available. Make sure it's installed ` + + `and on your path. Specific error: ${err}` + ); } } @@ -24,7 +26,7 @@ export async function verifyMkdocs () { * @param {string} outputDir directory mkdocs should build into * @param {?string} theme theme name */ -export async function mkdocsBuild (configPath, outputDir, theme = 'mkdocs') { +export async function mkdocsBuild(configPath, outputDir, theme = 'mkdocs') { await verifyMkdocs(); await exec('mkdocs', ['build', '-f', configPath, '-t', theme, '-d', outputDir]); } @@ -34,7 +36,7 @@ export async function mkdocsBuild (configPath, outputDir, theme = 'mkdocs') { * * @param {string} configPath path to mkdocs config yml */ -export async function mkdocsServe (configPath) { +export async function mkdocsServe(configPath) { await verifyMkdocs(); const proc = new SubProcess('mkdocs', ['serve', '-f', configPath]); await proc.start(); diff --git a/packages/docutils/test/unit/mike.spec.js b/packages/docutils/test/unit/mike.spec.js index 9065471ac..c6147a245 100644 --- a/packages/docutils/test/unit/mike.spec.js +++ b/packages/docutils/test/unit/mike.spec.js @@ -1,12 +1,26 @@ -import { Mike } from '../../lib'; -import { expect } from 'chai'; +import {Mike} from '../../lib'; +import {expect} from 'chai'; describe('Mike', function () { - it('should create args from params', function () { - const m = new Mike({configFile: '1', remote: '2', branch: '3', prefix: '4'}); + const m = new Mike({ + configFile: '1', + remote: '2', + branch: '3', + prefix: '4', + }); expect(m.getMikeArgs('cmd', ['arg1', 'arg2'])).eql([ - 'cmd', 'arg1', 'arg2', '--config-file', '1', '--remote', '2', '--branch', '3', '--prefix', '4' + 'cmd', + 'arg1', + 'arg2', + '--config-file', + '1', + '--remote', + '2', + '--branch', + '3', + '--prefix', + '4', ]); }); }); diff --git a/packages/eslint-config-appium/index.js b/packages/eslint-config-appium/index.js index 7c29cb4eb..f7c27cecf 100644 --- a/packages/eslint-config-appium/index.js +++ b/packages/eslint-config-appium/index.js @@ -29,7 +29,7 @@ module.exports = { 2, '1tbs', { - allowSingleLine: true, + allowSingleLine: true, }, ], 'comma-dangle': 0, @@ -58,7 +58,7 @@ module.exports = { 'comma-spacing': [ 2, { - before: false, + before: false, after: true, }, ], @@ -70,17 +70,17 @@ module.exports = { 'space-unary-ops': [ 2, { - words: true, - nonwords: false, + words: true, + nonwords: false, }, ], 'space-infix-ops': 2, 'key-spacing': [ 2, { - mode: 'strict', - beforeColon: false, - afterColon: true, + mode: 'strict', + beforeColon: false, + afterColon: true, }, ], 'no-multi-spaces': 2, @@ -88,8 +88,8 @@ module.exports = { 2, 'single', { - avoidEscape: true, - allowTemplateLiterals: true, + avoidEscape: true, + allowTemplateLiterals: true, }, ], 'no-buffer-constructor': 1, @@ -100,14 +100,12 @@ module.exports = { 'warn', { selector: 'AssignmentExpression[left.object.property.name="prototype"]', - message: - 'Avoid assignment to prototype; use class fields, methods or mixins instead.', + message: 'Avoid assignment to prototype; use class fields, methods or mixins instead.', }, { selector: 'CallExpression[callee.object.name="Object"][callee.property.name="assign"][arguments.0.property.name="prototype"]', - message: - 'Avoid assignment to prototype; use class fields, methods or mixins instead.', + message: 'Avoid assignment to prototype; use class fields, methods or mixins instead.', }, ], }, diff --git a/packages/execute-driver-plugin/lib/execute-child.js b/packages/execute-driver-plugin/lib/execute-child.js index 885e04b0d..eb2c9715a 100644 --- a/packages/execute-driver-plugin/lib/execute-child.js +++ b/packages/execute-driver-plugin/lib/execute-child.js @@ -1,8 +1,8 @@ import _ from 'lodash'; import B from 'bluebird'; -import { NodeVM } from 'vm2'; -import { logger, util } from '@appium/support'; -import { attach } from 'webdriverio'; +import {NodeVM} from 'vm2'; +import {logger, util} from '@appium/support'; +import {attach} from 'webdriverio'; const log = logger.getLogger('ExecuteDriver Child'); let send; @@ -12,7 +12,7 @@ let send; export const W3C_ELEMENT_KEY = util.W3C_WEB_ELEMENT_IDENTIFIER; export const MJSONWP_ELEMENT_KEY = 'ELEMENT'; -async function runScript (driverOpts, script, timeoutMs) { +async function runScript(driverOpts, script, timeoutMs) { if (!_.isNumber(timeoutMs)) { throw new TypeError('Timeout parameter must be a number'); } @@ -52,7 +52,7 @@ async function runScript (driverOpts, script, timeoutMs) { * * @return {string} - the full script to execute */ -function buildScript (script) { +function buildScript(script) { return `module.exports = async function execute (driver, console, Promise) { ${script} }`; @@ -68,14 +68,16 @@ function buildScript (script) { * * @return {Object} - safely converted object */ -function coerceScriptResult (obj) { +function coerceScriptResult(obj) { // first ensure obj is of a type that can be JSON encoded safely. This will // get rid of custom objects, functions, etc... and turn them into POJOs try { obj = JSON.parse(JSON.stringify(obj)); } catch (e) { - log.warn('Could not convert executeDriverScript to safe response!' + - `Result was: ${JSON.stringify(obj)}. Will make it null`); + log.warn( + 'Could not convert executeDriverScript to safe response!' + + `Result was: ${JSON.stringify(obj)}. Will make it null` + ); return null; } @@ -120,7 +122,7 @@ function coerceScriptResult (obj) { return obj; } -async function main (driverOpts, script, timeoutMs) { +async function main(driverOpts, script, timeoutMs) { let res; try { res = {success: await runScript(driverOpts, script, timeoutMs)}; diff --git a/packages/execute-driver-plugin/lib/plugin.js b/packages/execute-driver-plugin/lib/plugin.js index d7e107f1c..66e5e1907 100644 --- a/packages/execute-driver-plugin/lib/plugin.js +++ b/packages/execute-driver-plugin/lib/plugin.js @@ -1,7 +1,7 @@ import BasePlugin from '@appium/base-plugin'; import _ from 'lodash'; import cp from 'child_process'; -import { timing } from '@appium/support'; +import {timing} from '@appium/support'; import B from 'bluebird'; const FEAT_FLAG = 'execute_driver_script'; @@ -9,14 +9,13 @@ const DEFAULT_SCRIPT_TIMEOUT_MS = 1000 * 60 * 60; // default to 1 hour timeout const SCRIPT_TYPE_WDIO = 'webdriverio'; export default class ExecuteDriverPlugin extends BasePlugin { - static newMethodMap = { '/session/:sessionId/appium/execute_driver': { POST: { command: 'executeDriverScript', - payloadParams: {required: ['script'], optional: ['type', 'timeout']} - } - } + payloadParams: {required: ['script'], optional: ['type', 'timeout']}, + }, + }, }; /** @@ -34,13 +33,19 @@ export default class ExecuteDriverPlugin extends BasePlugin { * @returns {Object} - a JSONifiable object representing the return value of * the script */ - async executeDriverScript (next, driver, script, scriptType = 'webdriverio', - timeoutMs = DEFAULT_SCRIPT_TIMEOUT_MS) { - + async executeDriverScript( + next, + driver, + script, + scriptType = 'webdriverio', + timeoutMs = DEFAULT_SCRIPT_TIMEOUT_MS + ) { if (!driver.isFeatureEnabled(FEAT_FLAG)) { - throw new Error(`Execute driver script functionality is not available ` + - `unless server is started with --allow-insecure including ` + - `the '${FEAT_FLAG}' flag, e.g., --allow-insecure=${FEAT_FLAG}`); + throw new Error( + `Execute driver script functionality is not available ` + + `unless server is started with --allow-insecure including ` + + `the '${FEAT_FLAG}' flag, e.g., --allow-insecure=${FEAT_FLAG}` + ); } if (scriptType !== SCRIPT_TYPE_WDIO) { @@ -48,8 +53,10 @@ export default class ExecuteDriverPlugin extends BasePlugin { } if (!driver.serverHost || !driver.serverPort) { - throw new Error('Address or port of running server were not defined; this ' + - 'is required. This is probably a programming error in the driver'); + throw new Error( + 'Address or port of running server were not defined; this ' + + 'is required. This is probably a programming error in the driver' + ); } if (!_.isNumber(timeoutMs)) { @@ -66,10 +73,11 @@ export default class ExecuteDriverPlugin extends BasePlugin { path: driver.serverPath, isW3C: true, isMobile: true, - capabilities: driver.caps + capabilities: driver.caps, }; - this.logger.info(`Constructed webdriverio driver options; W3C mode is ${driverOpts.isW3C ? 'on' : 'off'}`); - + this.logger.info( + `Constructed webdriverio driver options; W3C mode is ${driverOpts.isW3C ? 'on' : 'off'}` + ); // fork the execution script as a child process const childScript = require.resolve('./execute-child.js'); @@ -90,7 +98,9 @@ export default class ExecuteDriverPlugin extends BasePlugin { scriptProc.on('message', res); // this is node IPC }); - this.logger.info('Received execute driver script result from child process, shutting it down'); + this.logger.info( + 'Received execute driver script result from child process, shutting it down' + ); if (res.error) { throw new Error(res.error.message); @@ -111,8 +121,10 @@ export default class ExecuteDriverPlugin extends BasePlugin { return; } - throw new Error(`Execute driver script timed out after ${timeoutMs}ms. ` + - `You can adjust this with the 'timeout' parameter.`); + throw new Error( + `Execute driver script timed out after ${timeoutMs}ms. ` + + `You can adjust this with the 'timeout' parameter.` + ); }; // now that the child script is alive, send it the data it needs to start @@ -144,4 +156,4 @@ export default class ExecuteDriverPlugin extends BasePlugin { } } -export { ExecuteDriverPlugin }; +export {ExecuteDriverPlugin}; diff --git a/packages/execute-driver-plugin/test/e2e/plugin.e2e.spec.js b/packages/execute-driver-plugin/test/e2e/plugin.e2e.spec.js index f4436e9fc..95b4e226e 100644 --- a/packages/execute-driver-plugin/test/e2e/plugin.e2e.spec.js +++ b/packages/execute-driver-plugin/test/e2e/plugin.e2e.spec.js @@ -2,30 +2,38 @@ import path from 'path'; -import { e2eSetup as _e2eSetup } from '@appium/base-plugin/build/test/helpers'; -import { remote as wdio } from 'webdriverio'; -import { W3C_ELEMENT_KEY, MJSONWP_ELEMENT_KEY } from '../../lib/execute-child'; -import { fs } from '@appium/support'; +import {e2eSetup as _e2eSetup} from '@appium/base-plugin/build/test/helpers'; +import {remote as wdio} from 'webdriverio'; +import {W3C_ELEMENT_KEY, MJSONWP_ELEMENT_KEY} from '../../lib/execute-child'; +import {fs} from '@appium/support'; const THIS_PLUGIN_DIR = path.join(__dirname, '..', '..'); const APPIUM_HOME = path.join(THIS_PLUGIN_DIR, 'local_appium_home'); const FAKE_DRIVER_DIR = path.join(THIS_PLUGIN_DIR, '..', 'fake-driver'); const TEST_HOST = 'localhost'; -const TEST_FAKE_APP = path.join(APPIUM_HOME, 'node_modules', '@appium', 'fake-driver', 'test', 'fixtures', 'app.xml'); +const TEST_FAKE_APP = path.join( + APPIUM_HOME, + 'node_modules', + '@appium', + 'fake-driver', + 'test', + 'fixtures', + 'app.xml' +); // XXX: remove when the test files get a proper TS configuration -const e2eSetup = /** @type {import('@appium/base-plugin/test/helpers').e2eSetup} */(_e2eSetup); +const e2eSetup = /** @type {import('@appium/base-plugin/test/helpers').e2eSetup} */ (_e2eSetup); const TEST_CAPS = { platformName: 'Fake', 'appium:automationName': 'Fake', 'appium:deviceName': 'Fake', - 'appium:app': TEST_FAKE_APP + 'appium:app': TEST_FAKE_APP, }; const WDIO_OPTS = { hostname: TEST_HOST, connectionRetryCount: 0, - capabilities: TEST_CAPS + capabilities: TEST_CAPS, }; describe('ExecuteDriverPlugin', function () { @@ -37,9 +45,16 @@ describe('ExecuteDriverPlugin', function () { * @type {import('@appium/base-plugin/test/helpers').E2ESetupOpts} */ const e2eSetupOpts = { - before, after, host: TEST_HOST, driverName: 'fake', driverSource: 'local', - driverSpec: FAKE_DRIVER_DIR, pluginName: 'execute-driver', pluginSource: 'local', - pluginSpec: THIS_PLUGIN_DIR, appiumHome: APPIUM_HOME + before, + after, + host: TEST_HOST, + driverName: 'fake', + driverSource: 'local', + driverSpec: FAKE_DRIVER_DIR, + pluginName: 'execute-driver', + pluginSource: 'local', + pluginSpec: THIS_PLUGIN_DIR, + appiumHome: APPIUM_HOME, }; after(async function () { @@ -47,22 +62,30 @@ describe('ExecuteDriverPlugin', function () { }); describe('without --allow-insecure set', function () { - after(async function () { driver && await driver.deleteSession(); }); + after(async function () { + driver && (await driver.deleteSession()); + }); e2eSetup({...e2eSetupOpts}); it('should not work unless the allowInsecure feature flag is set', async function () { driver = await wdio({...WDIO_OPTS, port: this.port}); - await driver.driverScript(basicScript).should.be - .rejectedWith(/allow-insecure.+execute_driver_script/i); + await driver + .driverScript(basicScript) + .should.be.rejectedWith(/allow-insecure.+execute_driver_script/i); }); - }); describe('with --allow-insecure set', function () { - - after(async function () { driver && await driver.deleteSession(); }); - e2eSetup({...e2eSetupOpts, serverArgs: {allowInsecure: ['execute_driver_script']}}); - before(async function () { driver = await wdio({...WDIO_OPTS, port: this.port}); }); + after(async function () { + driver && (await driver.deleteSession()); + }); + e2eSetup({ + ...e2eSetupOpts, + serverArgs: {allowInsecure: ['execute_driver_script']}, + }); + before(async function () { + driver = await wdio({...WDIO_OPTS, port: this.port}); + }); it('should execute a webdriverio script in the context of session', async function () { const script = ` @@ -80,8 +103,7 @@ describe('ExecuteDriverPlugin', function () { it('should fail with any script type other than webdriverio currently', async function () { const script = `return 'foo'`; - await driver.driverScript(script, 'wd') - .should.be.rejectedWith(/webdriverio/); + await driver.driverScript(script, 'wd').should.be.rejectedWith(/webdriverio/); }); it('should execute a webdriverio script that returns elements correctly', async function () { @@ -103,7 +125,7 @@ describe('ExecuteDriverPlugin', function () { const {result} = await driver.driverScript(script); const elObj = { [W3C_ELEMENT_KEY]: '1', - [MJSONWP_ELEMENT_KEY]: '1' + [MJSONWP_ELEMENT_KEY]: '1', }; result.should.eql({element: elObj, elements: [elObj, elObj]}); }); @@ -144,8 +166,9 @@ describe('ExecuteDriverPlugin', function () { const script = ` return {; `; - await driver.driverScript(script).should.be.rejectedWith( - /Could not execute driver script.+Unexpected token/); + await driver + .driverScript(script) + .should.be.rejectedWith(/Could not execute driver script.+Unexpected token/); }); it('should be able to set a timeout on a driver script', async function () { @@ -153,7 +176,8 @@ describe('ExecuteDriverPlugin', function () { await Promise.delay(1000); return true; `; - await driver.driverScript(script, 'webdriverio', 50) + await driver + .driverScript(script, 'webdriverio', 50) .should.be.rejectedWith(/.+50.+timeout.+/); }); }); diff --git a/packages/execute-driver-plugin/test/unit/plugin.spec.js b/packages/execute-driver-plugin/test/unit/plugin.spec.js index 984e10a57..58ad2f867 100644 --- a/packages/execute-driver-plugin/test/unit/plugin.spec.js +++ b/packages/execute-driver-plugin/test/unit/plugin.spec.js @@ -1,4 +1,4 @@ -import { ExecuteDriverPlugin } from '../../lib/plugin'; +import {ExecuteDriverPlugin} from '../../lib/plugin'; describe('execute driver plugin', function () { it('should exist', function () { diff --git a/packages/fake-driver/lib/commands/alert.js b/packages/fake-driver/lib/commands/alert.js index ab184e65a..21bc01ebe 100644 --- a/packages/fake-driver/lib/commands/alert.js +++ b/packages/fake-driver/lib/commands/alert.js @@ -1,25 +1,27 @@ -import { errors } from '@appium/base-driver'; +import {errors} from '@appium/base-driver'; -let commands = {}, helpers = {}, extensions = {}; +let commands = {}, + helpers = {}, + extensions = {}; -helpers.assertNoAlert = function assertNoAlert () { +helpers.assertNoAlert = function assertNoAlert() { if (this.appModel.hasAlert()) { throw new errors.UnexpectedAlertOpenError(); } }; -helpers.assertAlert = function assertAlert () { +helpers.assertAlert = function assertAlert() { if (!this.appModel.hasAlert()) { throw new errors.NoAlertOpenError(); } }; -commands.getAlertText = async function getAlertText () { +commands.getAlertText = async function getAlertText() { this.assertAlert(); return this.appModel.alertText(); }; -commands.setAlertText = async function setAlertText (text) { +commands.setAlertText = async function setAlertText(text) { this.assertAlert(); try { this.appModel.setAlertText(text); @@ -28,7 +30,7 @@ commands.setAlertText = async function setAlertText (text) { } }; -commands.postAcceptAlert = async function postAcceptAlert () { +commands.postAcceptAlert = async function postAcceptAlert() { this.assertAlert(); this.appModel.handleAlert(); }; @@ -36,5 +38,5 @@ commands.postAcceptAlert = async function postAcceptAlert () { commands.postDismissAlert = commands.postAcceptAlert; Object.assign(extensions, commands, helpers); -export { commands, helpers }; +export {commands, helpers}; export default extensions; diff --git a/packages/fake-driver/lib/commands/contexts.js b/packages/fake-driver/lib/commands/contexts.js index 549c78416..6ef8ff3e1 100644 --- a/packages/fake-driver/lib/commands/contexts.js +++ b/packages/fake-driver/lib/commands/contexts.js @@ -1,9 +1,11 @@ import _ from 'lodash'; -import { errors } from '@appium/base-driver'; +import {errors} from '@appium/base-driver'; -let commands = {}, helpers = {}, extensions = {}; +let commands = {}, + helpers = {}, + extensions = {}; -helpers.getRawContexts = function getRawContexts () { +helpers.getRawContexts = function getRawContexts() { let contexts = {NATIVE_APP: null, PROXY: null}; let wvs = this.appModel.getWebviews(); for (let i = 1; i < wvs.length + 1; i++) { @@ -12,21 +14,21 @@ helpers.getRawContexts = function getRawContexts () { return contexts; }; -helpers.assertWebviewContext = function assertWebviewContext () { +helpers.assertWebviewContext = function assertWebviewContext() { if (this.curContext === 'NATIVE_APP') { throw new errors.InvalidContextError(); } }; -commands.getCurrentContext = async function getCurrentContext () { +commands.getCurrentContext = async function getCurrentContext() { return this.curContext; }; -commands.getContexts = async function getContexts () { +commands.getContexts = async function getContexts() { return _.keys(this.getRawContexts()); }; -commands.setContext = async function setContext (context) { +commands.setContext = async function setContext(context) { let contexts = this.getRawContexts(); if (_.includes(_.keys(contexts), context)) { this.curContext = context; @@ -44,7 +46,7 @@ commands.setContext = async function setContext (context) { } }; -commands.setFrame = async function setFrame (frameId) { +commands.setFrame = async function setFrame(frameId) { this.assertWebviewContext(); if (frameId === null) { this.appModel.deactivateFrame(); @@ -58,5 +60,5 @@ commands.setFrame = async function setFrame (frameId) { }; Object.assign(extensions, commands, helpers); -export { commands, helpers }; +export {commands, helpers}; export default extensions; diff --git a/packages/fake-driver/lib/commands/element.js b/packages/fake-driver/lib/commands/element.js index 6207c87a5..92713b280 100644 --- a/packages/fake-driver/lib/commands/element.js +++ b/packages/fake-driver/lib/commands/element.js @@ -1,9 +1,11 @@ import _ from 'lodash'; -import { errors } from '@appium/base-driver'; +import {errors} from '@appium/base-driver'; -let commands = {}, helpers = {}, extensions = {}; +let commands = {}, + helpers = {}, + extensions = {}; -helpers.getElements = function getElements (elIds) { +helpers.getElements = function getElements(elIds) { for (let elId of elIds) { if (!_.has(this.elMap, elId)) { throw new errors.StaleElementReferenceError(); @@ -12,31 +14,31 @@ helpers.getElements = function getElements (elIds) { return elIds.map((e) => this.elMap[e]); }; -helpers.getElement = function getElement (elId) { +helpers.getElement = function getElement(elId) { return this.getElements([elId])[0]; }; -commands.getName = async function getName (elementId) { +commands.getName = async function getName(elementId) { let el = this.getElement(elementId); return el.tagName; }; -commands.elementDisplayed = async function elementDisplayed (elementId) { +commands.elementDisplayed = async function elementDisplayed(elementId) { let el = this.getElement(elementId); return el.isVisible(); }; -commands.elementEnabled = async function elementEnabled (elementId) { +commands.elementEnabled = async function elementEnabled(elementId) { let el = this.getElement(elementId); return el.isEnabled(); }; -commands.elementSelected = async function elementSelected (elementId) { +commands.elementSelected = async function elementSelected(elementId) { let el = this.getElement(elementId); return el.isSelected(); }; -commands.setValue = async function setValue (keys, elementId) { +commands.setValue = async function setValue(keys, elementId) { let value = keys; if (keys instanceof Array) { value = keys.join(''); @@ -48,16 +50,16 @@ commands.setValue = async function setValue (keys, elementId) { el.setAttr('value', value); }; -commands.getText = async function getText (elementId) { +commands.getText = async function getText(elementId) { let el = this.getElement(elementId); return el.getAttr('value'); }; -commands.clear = async function clear (elementId) { +commands.clear = async function clear(elementId) { await this.setValue('', elementId); }; -commands.click = async function click (elementId) { +commands.click = async function click(elementId) { this.assertNoAlert(); let el = this.getElement(elementId); if (!el.isVisible()) { @@ -67,22 +69,22 @@ commands.click = async function click (elementId) { this.focusedElId = elementId; }; -commands.getAttribute = async function getAttribute (attr, elementId) { +commands.getAttribute = async function getAttribute(attr, elementId) { let el = this.getElement(elementId); return el.getAttr(attr); }; -commands.getElementRect = function getElementRect (elementId) { +commands.getElementRect = function getElementRect(elementId) { let el = this.getElement(elementId); return el.getElementRect(); }; -commands.getSize = function getSize (elementId) { +commands.getSize = function getSize(elementId) { let el = this.getElement(elementId); return el.getSize(); }; -commands.equalsElement = function equalsElement (el1Id, el2Id) { +commands.equalsElement = function equalsElement(el1Id, el2Id) { let el1 = this.getElement(el1Id); let el2 = this.getElement(el2Id); return el1.equals(el2); @@ -90,12 +92,12 @@ commands.equalsElement = function equalsElement (el1Id, el2Id) { commands.getLocationInView = commands.getLocation; -commands.getCssProperty = async function getCssProperty (prop, elementId) { +commands.getCssProperty = async function getCssProperty(prop, elementId) { this.assertWebviewContext(); let el = this.getElement(elementId); return el.getCss(prop); }; Object.assign(extensions, commands, helpers); -export { commands, helpers }; +export {commands, helpers}; export default extensions; diff --git a/packages/fake-driver/lib/commands/find.js b/packages/fake-driver/lib/commands/find.js index d7a2e3764..3e87e5708 100644 --- a/packages/fake-driver/lib/commands/find.js +++ b/packages/fake-driver/lib/commands/find.js @@ -1,10 +1,12 @@ import _ from 'lodash'; -import { errors } from '@appium/base-driver'; -import { FakeElement } from '../fake-element'; +import {errors} from '@appium/base-driver'; +import {FakeElement} from '../fake-element'; -let commands = {}, helpers = {}, extensions = {}; +let commands = {}, + helpers = {}, + extensions = {}; -helpers.getExistingElementForNode = function getExistingElementForNode (node) { +helpers.getExistingElementForNode = function getExistingElementForNode(node) { for (let [id, el] of _.toPairs(this.elMap)) { if (el.node === node) { return id; @@ -13,7 +15,7 @@ helpers.getExistingElementForNode = function getExistingElementForNode (node) { return null; }; -helpers.wrapNewEl = function wrapNewEl (obj) { +helpers.wrapNewEl = function wrapNewEl(obj) { // first check and see if we already have a ref to this element let existingElId = this.getExistingElementForNode(obj); if (existingElId) { @@ -26,14 +28,14 @@ helpers.wrapNewEl = function wrapNewEl (obj) { return {ELEMENT: this.maxElId.toString()}; }; -helpers.findElOrEls = async function findElOrEls (strategy, selector, mult, ctx) { +helpers.findElOrEls = async function findElOrEls(strategy, selector, mult, ctx) { let qMap = { - 'xpath': 'xpathQuery', - 'id': 'idQuery', + xpath: 'xpathQuery', + id: 'idQuery', 'accessibility id': 'idQuery', 'class name': 'classQuery', 'tag name': 'classQuery', - 'css selector': 'cssQuery' + 'css selector': 'cssQuery', }; // TODO this error checking should probably be part of MJSONWP? if (!_.includes(_.keys(qMap), strategy)) { @@ -60,24 +62,32 @@ helpers.findElOrEls = async function findElOrEls (strategy, selector, mult, ctx) } }; -commands.findElement = async function findElement (strategy, selector) { +commands.findElement = async function findElement(strategy, selector) { return this.findElOrEls(strategy, selector, false); }; -commands.findElements = async function findElements (strategy, selector) { +commands.findElements = async function findElements(strategy, selector) { return this.findElOrEls(strategy, selector, true); }; -commands.findElementFromElement = async function findElementFromElement (strategy, selector, elementId) { +commands.findElementFromElement = async function findElementFromElement( + strategy, + selector, + elementId +) { let el = this.getElement(elementId); return this.findElOrEls(strategy, selector, false, el.xmlFragment); }; -commands.findElementsFromElement = async function findElementsFromElement (strategy, selector, elementId) { +commands.findElementsFromElement = async function findElementsFromElement( + strategy, + selector, + elementId +) { let el = this.getElement(elementId); return this.findElOrEls(strategy, selector, true, el.xmlFragment); }; Object.assign(extensions, commands, helpers); -export { commands, helpers}; +export {commands, helpers}; export default extensions; diff --git a/packages/fake-driver/lib/commands/general.js b/packages/fake-driver/lib/commands/general.js index 1782f999d..e46437027 100644 --- a/packages/fake-driver/lib/commands/general.js +++ b/packages/fake-driver/lib/commands/general.js @@ -1,65 +1,66 @@ import _ from 'lodash'; -import { errors } from '@appium/base-driver'; +import {errors} from '@appium/base-driver'; -let commands = {}, helpers = {}, extensions = {}; +let commands = {}, + helpers = {}, + extensions = {}; -commands.title = async function title () { +commands.title = async function title() { this.assertWebviewContext(); return this.appModel.title; }; -commands.keys = async function keys (value) { +commands.keys = async function keys(value) { if (!this.focusedElId) { throw new errors.InvalidElementStateError(); } await this.setValue(value, this.focusedElId); }; -commands.setGeoLocation = async function setGeoLocation (location) { +commands.setGeoLocation = async function setGeoLocation(location) { // TODO test this adequately once WD bug is fixed this.appModel.lat = location.latitude; this.appModel.long = location.longitude; }; -commands.getGeoLocation = async function getGeoLocation () { +commands.getGeoLocation = async function getGeoLocation() { return this.appModel.currentGeoLocation; }; -commands.getPageSource = async function getPageSource () { +commands.getPageSource = async function getPageSource() { return this.appModel.rawXml; }; -commands.getOrientation = async function getOrientation () { +commands.getOrientation = async function getOrientation() { return this.appModel.orientation; }; -commands.setOrientation = async function setOrientation (o) { +commands.setOrientation = async function setOrientation(o) { if (!_.includes(['LANDSCAPE', 'PORTRAIT'], o)) { throw new errors.UnknownError('Orientation must be LANDSCAPE or PORTRAIT'); } this.appModel.orientation = o; }; -commands.getScreenshot = async function getScreenshot () { +commands.getScreenshot = async function getScreenshot() { return this.appModel.getScreenshot(); }; -commands.getWindowSize = async function getWindowSize () { +commands.getWindowSize = async function getWindowSize() { return {width: this.appModel.width, height: this.appModel.height}; }; -commands.getWindowRect = async function getWindowRect () { +commands.getWindowRect = async function getWindowRect() { return {width: this.appModel.width, height: this.appModel.height, x: 0, y: 0}; }; -commands.performActions = async function performActions (actions) { +commands.performActions = async function performActions(actions) { this.appModel.actionLog.push(actions); }; -commands.releaseActions = async function releaseActions () { -}; +commands.releaseActions = async function releaseActions() {}; -commands.getLog = async function getLog (type) { +commands.getLog = async function getLog(type) { switch (type) { case 'actions': return this.appModel.actionLog; @@ -69,5 +70,5 @@ commands.getLog = async function getLog (type) { }; Object.assign(extensions, commands, helpers); -export { commands, helpers }; +export {commands, helpers}; export default extensions; diff --git a/packages/fake-driver/lib/commands/index.js b/packages/fake-driver/lib/commands/index.js index e93afe8a5..05371dc53 100644 --- a/packages/fake-driver/lib/commands/index.js +++ b/packages/fake-driver/lib/commands/index.js @@ -7,12 +7,12 @@ import alertCommands from './alert'; let commands = {}; Object.assign( - commands, - contextCommands, - findCommands, - elementCommands, - generalCommands, - alertCommands + commands, + contextCommands, + findCommands, + elementCommands, + generalCommands, + alertCommands ); export default commands; diff --git a/packages/fake-driver/lib/driver.js b/packages/fake-driver/lib/driver.js index 473351975..081500f44 100644 --- a/packages/fake-driver/lib/driver.js +++ b/packages/fake-driver/lib/driver.js @@ -1,11 +1,11 @@ import B from 'bluebird'; import _ from 'lodash'; -import { BaseDriver, errors } from '@appium/base-driver'; -import { FakeApp } from './fake-app'; +import {BaseDriver, errors} from '@appium/base-driver'; +import {FakeApp} from './fake-app'; import commands from './commands'; class FakeDriver extends BaseDriver { - constructor (opts = {}, shouldValidateCaps = true) { + constructor(opts = {}, shouldValidateCaps = true) { super(opts, shouldValidateCaps); this.appModel = null; this.curContext = 'NATIVE_APP'; @@ -18,22 +18,22 @@ class FakeDriver extends BaseDriver { this._proxyActive = false; this.desiredCapConstraints = { - 'app': { + app: { presence: true, - isString: true - } + isString: true, + }, }; } - proxyActive () { + proxyActive() { return this._proxyActive; } - canProxy () { + canProxy() { return true; } - proxyReqRes (req, res) { + proxyReqRes(req, res) { // fake implementation of jwp proxy req res res.set('content-type', 'application/json'); const resBodyObj = {value: 'proxied via proxyReqRes'}; @@ -42,12 +42,16 @@ class FakeDriver extends BaseDriver { res.status(200).send(JSON.stringify(resBodyObj)); } - proxyCommand (/*url, method, body*/) { + proxyCommand(/*url, method, body*/) { return 'proxied via proxyCommand'; } - async createSession (jsonwpDesiredCapabilities, jsonwpRequiredCaps, w3cCapabilities, otherSessionData = []) { - + async createSession( + jsonwpDesiredCapabilities, + jsonwpRequiredCaps, + w3cCapabilities, + otherSessionData = [] + ) { // TODO add validation on caps.app that we will get for free from // BaseDriver @@ -55,12 +59,19 @@ class FakeDriver extends BaseDriver { // not being able to start a session because of system resources for (let d of otherSessionData) { if (d.isUnique) { - throw new errors.SessionNotCreatedError('Cannot start session; another ' + - 'unique session is in progress that requires all resources'); + throw new errors.SessionNotCreatedError( + 'Cannot start session; another ' + + 'unique session is in progress that requires all resources' + ); } } - let [sessionId, caps] = await super.createSession(jsonwpDesiredCapabilities, jsonwpRequiredCaps, w3cCapabilities, otherSessionData); + let [sessionId, caps] = await super.createSession( + jsonwpDesiredCapabilities, + jsonwpRequiredCaps, + w3cCapabilities, + otherSessionData + ); this.appModel = new FakeApp(); if (_.isArray(caps) === true && caps.length === 1) { caps = caps[0]; @@ -70,24 +81,24 @@ class FakeDriver extends BaseDriver { return [sessionId, caps]; } - get driverData () { + get driverData() { return { - isUnique: !!this.caps.uniqueApp + isUnique: !!this.caps.uniqueApp, }; } - async getFakeThing () { + async getFakeThing() { await B.delay(1); return this.fakeThing; } - async setFakeThing (thing) { + async setFakeThing(thing) { await B.delay(1); this.fakeThing = thing; return null; } - async getFakeDriverArgs () { + async getFakeDriverArgs() { await B.delay(1); return this.cliArgs; } @@ -95,23 +106,23 @@ class FakeDriver extends BaseDriver { static newMethodMap = { '/session/:sessionId/fakedriver': { GET: {command: 'getFakeThing'}, - POST: {command: 'setFakeThing', payloadParams: {required: ['thing']}} + POST: {command: 'setFakeThing', payloadParams: {required: ['thing']}}, }, '/session/:sessionId/fakedriverargs': { - GET: {command: 'getFakeDriverArgs'} + GET: {command: 'getFakeDriverArgs'}, }, }; - static fakeRoute (req, res) { + static fakeRoute(req, res) { res.send(JSON.stringify({fakedriver: 'fakeResponse'})); } - static async updateServer (expressApp/*, httpServer*/) { // eslint-disable-line require-await + static async updateServer(expressApp /*, httpServer*/) { + // eslint-disable-line require-await expressApp.all('/fakedriver', FakeDriver.fakeRoute); } - } Object.assign(FakeDriver.prototype, commands); -export { FakeDriver }; +export {FakeDriver}; diff --git a/packages/fake-driver/lib/fake-app.js b/packages/fake-driver/lib/fake-app.js index c66e094e4..90b394c86 100644 --- a/packages/fake-driver/lib/fake-app.js +++ b/packages/fake-driver/lib/fake-app.js @@ -1,15 +1,15 @@ -import { fs } from '@appium/support'; -import { readFileSync } from 'fs'; +import {fs} from '@appium/support'; +import {readFileSync} from 'fs'; import path from 'path'; import XMLDom from '@xmldom/xmldom'; import xpath from 'xpath'; import log from './logger'; -import { FakeElement } from './fake-element'; +import {FakeElement} from './fake-element'; const SCREENSHOT = path.join(__dirname, 'screen.png'); class FakeApp { - constructor () { + constructor() { this.dom = null; this.activeDom = null; this.activeWebview = null; @@ -24,7 +24,7 @@ class FakeApp { this.actionLog = []; } - get title () { + get title() { let nodes = this.xpathQuery('//title'); if (nodes.length < 1) { throw new Error('No title!'); @@ -32,43 +32,43 @@ class FakeApp { return nodes[0].firstChild.data; } - get currentGeoLocation () { + get currentGeoLocation() { return { latitude: this.lat, - longitude: this.long + longitude: this.long, }; } - get orientation () { + get orientation() { return this.currentOrientation; } - set orientation (o) { + set orientation(o) { this.currentOrientation = o; } - get width () { + get width() { if (this._width === null) { this.setDims(); } return this._width; } - get height () { + get height() { if (this._width === null) { this.setDims(); } return this._width; } - setDims () { + setDims() { const nodes = this.xpathQuery('//app'); const app = new FakeElement(nodes[0], this); this._width = parseInt(app.nodeAttrs.width, 10); this._height = parseInt(app.nodeAttrs.height, 10); } - async loadApp (appPath) { + async loadApp(appPath) { log.info(`Loading Mock app model at ${appPath}`); let data = await fs.readFile(appPath); log.info('Parsing Mock app XML'); @@ -80,47 +80,45 @@ class FakeApp { this.activeDom = this.dom; } - getWebviews () { + getWebviews() { return this.xpathQuery('//MockWebView/*[1]').map((n) => new FakeWebView(n)); } - activateWebview (wv) { + activateWebview(wv) { this.activeWebview = wv; let fragment = new XMLDom.XMLSerializer().serializeToString(wv.node); - this.activeDom = new XMLDom.DOMParser().parseFromString(fragment, - 'application/xml'); + this.activeDom = new XMLDom.DOMParser().parseFromString(fragment, 'application/xml'); } - deactivateWebview () { + deactivateWebview() { this.activeWebview = null; this.activeDom = this.dom; } - activateFrame (frame) { + activateFrame(frame) { this.activeFrame = frame; let fragment = new XMLDom.XMLSerializer().serializeToString(frame); - this.activeDom = new XMLDom.DOMParser().parseFromString(fragment, - 'application/xml'); + this.activeDom = new XMLDom.DOMParser().parseFromString(fragment, 'application/xml'); } - deactivateFrame () { + deactivateFrame() { this.activeFrame = null; this.activateWebview(this.activeWebview); } - xpathQuery (sel, ctx) { + xpathQuery(sel, ctx) { return xpath.select(sel, ctx || this.activeDom); } - idQuery (id, ctx) { + idQuery(id, ctx) { return this.xpathQuery(`//*[@id="${id}"]`, ctx); } - classQuery (className, ctx) { + classQuery(className, ctx) { return this.xpathQuery(`//${className}`, ctx); } - cssQuery (css, ctx) { + cssQuery(css, ctx) { if (css.startsWith('#')) { return this.idQuery(css.slice(1), ctx); } @@ -130,18 +128,18 @@ class FakeApp { return this.classQuery(css, ctx); } - hasAlert () { + hasAlert() { return this.activeAlert !== null; } - setAlertText (text) { + setAlertText(text) { if (!this.activeAlert.hasPrompt()) { throw new Error('No prompt to set text of'); } this.activeAlert.setAttr('prompt', text); } - showAlert (alertId) { + showAlert(alertId) { let nodes = this.xpathQuery(`//alert[@id="${alertId}"]`); if (nodes.length < 1) { throw new Error(`Alert ${alertId} doesn't exist!`); @@ -149,25 +147,23 @@ class FakeApp { this.activeAlert = new FakeElement(nodes[0], this); } - alertText () { - return this.activeAlert.getAttr('prompt') || - this.activeAlert.nodeAttrs.text; + alertText() { + return this.activeAlert.getAttr('prompt') || this.activeAlert.nodeAttrs.text; } - handleAlert () { + handleAlert() { this.activeAlert = null; } - getScreenshot () { + getScreenshot() { return readFileSync(SCREENSHOT, 'base64'); } - } class FakeWebView { - constructor (node) { + constructor(node) { this.node = node; } } -export { FakeApp }; +export {FakeApp}; diff --git a/packages/fake-driver/lib/fake-driver-schema.js b/packages/fake-driver/lib/fake-driver-schema.js index da70ae245..251e510d1 100644 --- a/packages/fake-driver/lib/fake-driver-schema.js +++ b/packages/fake-driver/lib/fake-driver-schema.js @@ -14,7 +14,6 @@ // } // } - export default { type: 'object', title: 'Fake Driver Configuration', @@ -30,7 +29,7 @@ export default { sillyWebServerHost: { type: 'string', description: 'The host to use for the fake web server', - default: 'sillyhost' - } - } + default: 'sillyhost', + }, + }, }; diff --git a/packages/fake-driver/lib/fake-element.js b/packages/fake-driver/lib/fake-element.js index 8a022b4cf..8ff9a87f1 100644 --- a/packages/fake-driver/lib/fake-element.js +++ b/packages/fake-driver/lib/fake-element.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import XMLDom from '@xmldom/xmldom'; class FakeElement { - constructor (xmlNode, app) { + constructor(xmlNode, app) { this.app = app; this.node = xmlNode; this.nodeAttrs = {}; @@ -15,7 +15,7 @@ class FakeElement { this.parseCss(); } - parseCss () { + parseCss() { if (this.nodeAttrs.style) { let segments = this.nodeAttrs.style.split(';'); for (let s of segments) { @@ -27,49 +27,49 @@ class FakeElement { } } - get tagName () { + get tagName() { return this.node.tagName; } - setAttr (k, v) { + setAttr(k, v) { this.attrs[k] = v; } - getAttr (k) { + getAttr(k) { return this.attrs[k] || ''; } - isVisible () { + isVisible() { return this.nodeAttrs.visible !== 'false'; } - isEnabled () { + isEnabled() { return this.nodeAttrs.enabled !== 'false'; } - isSelected () { + isSelected() { return this.nodeAttrs.selected === 'true'; } - getLocation () { + getLocation() { return { x: parseFloat(this.nodeAttrs.left || 0), - y: parseFloat(this.nodeAttrs.top || 0) + y: parseFloat(this.nodeAttrs.top || 0), }; } - getElementRect () { + getElementRect() { return {...this.getLocation(), ...this.getSize()}; } - getSize () { + getSize() { return { width: parseFloat(this.nodeAttrs.width || 0), - height: parseFloat(this.nodeAttrs.height || 0) + height: parseFloat(this.nodeAttrs.height || 0), }; } - click () { + click() { let curClicks = this.getAttr('clicks') || 0; this.setAttr('clicks', curClicks + 1); let alertId = this.nodeAttrs.showAlert; @@ -78,26 +78,25 @@ class FakeElement { } } - equals (other) { + equals(other) { return this.node === other.node; } - hasPrompt () { + hasPrompt() { return this.nodeAttrs.hasPrompt === 'true'; } - getCss (prop) { + getCss(prop) { if (_.has(this.css, prop)) { return this.css[prop]; } return null; } - get xmlFragment () { + get xmlFragment() { let frag = new XMLDom.XMLSerializer().serializeToString(this.node); return new XMLDom.DOMParser().parseFromString(frag, 'application/xml'); } - } -export { FakeElement }; +export {FakeElement}; diff --git a/packages/fake-driver/lib/index.js b/packages/fake-driver/lib/index.js index e334df87b..2010dfbb4 100644 --- a/packages/fake-driver/lib/index.js +++ b/packages/fake-driver/lib/index.js @@ -3,13 +3,13 @@ import * as driver from './driver'; import * as server from './server'; -const { FakeDriver } = driver; -const { startServer } = server; +const {FakeDriver} = driver; +const {startServer} = server; const DEFAULT_HOST = 'localhost'; const DEFAULT_PORT = 4774; -async function main () { +async function main() { const getArgValue = (argName) => { const argIndex = process.argv.indexOf(argName); return argIndex > 0 ? process.argv[argIndex + 1] : null; @@ -19,4 +19,4 @@ async function main () { return await startServer(port, host); } -export { FakeDriver, startServer, main }; +export {FakeDriver, startServer, main}; diff --git a/packages/fake-driver/lib/logger.js b/packages/fake-driver/lib/logger.js index 249e2bfc3..beaf4836b 100644 --- a/packages/fake-driver/lib/logger.js +++ b/packages/fake-driver/lib/logger.js @@ -1,5 +1,4 @@ -import { logger } from '@appium/support'; - +import {logger} from '@appium/support'; const log = logger.getLogger('FakeDriver'); diff --git a/packages/fake-driver/lib/server.js b/packages/fake-driver/lib/server.js index 8f39fbf72..1618a43d0 100644 --- a/packages/fake-driver/lib/server.js +++ b/packages/fake-driver/lib/server.js @@ -1,9 +1,8 @@ import log from './logger'; -import { server as baseServer, routeConfiguringFunction } from '@appium/base-driver'; -import { FakeDriver } from './driver'; +import {server as baseServer, routeConfiguringFunction} from '@appium/base-driver'; +import {FakeDriver} from './driver'; - -async function startServer (port, hostname) { +async function startServer(port, hostname) { const d = new FakeDriver(); const server = await baseServer({ routeConfiguringFunction: routeConfiguringFunction(d), @@ -14,4 +13,4 @@ async function startServer (port, hostname) { return server; } -export { startServer }; +export {startServer}; diff --git a/packages/fake-driver/test/e2e/alert-tests.js b/packages/fake-driver/test/e2e/alert-tests.js index 8dfe5059b..98cea7575 100644 --- a/packages/fake-driver/test/e2e/alert-tests.js +++ b/packages/fake-driver/test/e2e/alert-tests.js @@ -1,9 +1,9 @@ -import { initSession, deleteSession, W3C_PREFIXED_CAPS } from '../helpers'; +import {initSession, deleteSession, W3C_PREFIXED_CAPS} from '../helpers'; -function alertTests () { +function alertTests() { describe('alerts', function () { let driver; - before (async function () { + before(async function () { driver = await initSession(W3C_PREFIXED_CAPS); }); after(async function () { @@ -25,32 +25,23 @@ function alertTests () { (await driver.getAlertText()).should.equal('foo'); }); it('should not do other things while an alert is there', async function () { - await (await driver.$('#nav')).click() - .should.eventually.be.rejectedWith({code: 26}); + await (await driver.$('#nav')).click().should.eventually.be.rejectedWith({code: 26}); }); it.skip('should accept an alert', function () { - driver - .acceptAlert() - .$('nav') - .click() - .nodeify(); + driver.acceptAlert().$('nav').click().nodeify(); }); it.skip('should not set the text of the wrong kind of alert', function () { driver .$('AlertButton2') .click() .alertText() - .should.eventually.become('Fake Alert 2') + .should.eventually.become('Fake Alert 2') .alertKeys('foo') - .should.be.rejectedWith(/12/) + .should.be.rejectedWith(/12/) .nodeify(); }); it.skip('should dismiss an alert', function () { - driver - .acceptAlert() - .$('nav') - .click() - .nodeify(); + driver.acceptAlert().$('nav').click().nodeify(); }); }); } diff --git a/packages/fake-driver/test/e2e/context-tests.js b/packages/fake-driver/test/e2e/context-tests.js index 4a5d0bffe..5111b377a 100644 --- a/packages/fake-driver/test/e2e/context-tests.js +++ b/packages/fake-driver/test/e2e/context-tests.js @@ -1,33 +1,31 @@ -import { initSession, deleteSession, W3C_PREFIXED_CAPS } from '../helpers'; +import {initSession, deleteSession, W3C_PREFIXED_CAPS} from '../helpers'; -function contextTests () { +function contextTests() { describe('contexts, webviews, frames', function () { let driver; - before (async function () { + before(async function () { driver = await initSession(W3C_PREFIXED_CAPS); }); after(async function () { return await deleteSession(driver); }); it('should get current context', async function () { - await driver.getContext() - .should.eventually.become('NATIVE_APP'); + await driver.getContext().should.eventually.become('NATIVE_APP'); }); it('should get contexts', async function () { - await driver.getContexts() - .should.eventually.become(['NATIVE_APP', 'PROXY', 'WEBVIEW_1']); + await driver.getContexts().should.eventually.become(['NATIVE_APP', 'PROXY', 'WEBVIEW_1']); }); it('should not set context that is not there', async function () { - await driver.switchContext('WEBVIEW_FOO') - .should.eventually.be.rejectedWith(/No such context found/); + await driver + .switchContext('WEBVIEW_FOO') + .should.eventually.be.rejectedWith(/No such context found/); }); it('should set context', async function () { await driver.switchContext('WEBVIEW_1'); await driver.getContext().should.eventually.become('WEBVIEW_1'); }); it('should find webview elements in a webview', async function () { - await (await driver.$('//*')).getTagName() - .should.eventually.become('html'); + await (await driver.$('//*')).getTagName().should.eventually.become('html'); }); it('should not switch to a frame that is not there', async function () { await driver.switchToFrame(2).should.eventually.be.rejectedWith(/frame could not be found/); @@ -46,7 +44,9 @@ function contextTests () { }); it('should not set a frame in a native context', async function () { await driver.switchContext('NATIVE_APP'); - await driver.switchToFrame(1).should.eventually.be.rejectedWith(/could not be executed in the current context/); + await driver + .switchToFrame(1) + .should.eventually.be.rejectedWith(/could not be executed in the current context/); }); }); } diff --git a/packages/fake-driver/test/e2e/driver.e2e.spec.js b/packages/fake-driver/test/e2e/driver.e2e.spec.js index be2a62eb6..3e8ba763b 100644 --- a/packages/fake-driver/test/e2e/driver.e2e.spec.js +++ b/packages/fake-driver/test/e2e/driver.e2e.spec.js @@ -1,19 +1,24 @@ // transpile:mocha import axios from 'axios'; -import { baseDriverE2ETests } from '@appium/base-driver/build/test/basedriver'; -import { FakeDriver, startServer } from '../../lib/index.js'; -import { BASE_CAPS, deleteSession, initSession, TEST_HOST, TEST_PORT, W3C_PREFIXED_CAPS } from '../helpers'; +import {baseDriverE2ETests} from '@appium/base-driver/build/test/basedriver'; +import {FakeDriver, startServer} from '../../lib/index.js'; +import { + BASE_CAPS, + deleteSession, + initSession, + TEST_HOST, + TEST_PORT, + W3C_PREFIXED_CAPS, +} from '../helpers'; import contextTests from './context-tests'; import findElementTests from './find-element-tests'; import elementInteractionTests from './element-interaction-tests'; import alertTests from './alert-tests'; import generalTests from './general-tests'; - const shouldStartServer = process.env.USE_RUNNING_SERVER !== '0'; - // test the same things as for base driver baseDriverE2ETests(FakeDriver, W3C_PREFIXED_CAPS); @@ -53,10 +58,9 @@ describe('FakeDriver - via HTTP', function () { const res = await axios.post(`http://${TEST_HOST}:${TEST_PORT}/session`, { capabilities: { alwaysMatch: W3C_PREFIXED_CAPS, - firstMatch: [ - {'appium:fakeCap': 'Foo', } - ] - }}); + firstMatch: [{'appium:fakeCap': 'Foo'}], + }, + }); const {value, status} = res.data; value.capabilities.should.deep.equal({...BASE_CAPS, fakeCap: 'Foo'}); value.sessionId.should.exist; @@ -65,10 +69,11 @@ describe('FakeDriver - via HTTP', function () { }); it('should fail if given unsupported desiredCapabilities', async function () { - await axios.post(`http://${TEST_HOST}:${TEST_PORT}/session`, { - desiredCapabilities: W3C_PREFIXED_CAPS - }).should.eventually.be.rejectedWith(/500/); + await axios + .post(`http://${TEST_HOST}:${TEST_PORT}/session`, { + desiredCapabilities: W3C_PREFIXED_CAPS, + }) + .should.eventually.be.rejectedWith(/500/); }); }); - }); diff --git a/packages/fake-driver/test/e2e/element-interaction-tests.js b/packages/fake-driver/test/e2e/element-interaction-tests.js index 7ae1bcd6a..cb9155f29 100644 --- a/packages/fake-driver/test/e2e/element-interaction-tests.js +++ b/packages/fake-driver/test/e2e/element-interaction-tests.js @@ -1,11 +1,11 @@ import chaiWebdriverIOAsync from 'chai-webdriverio-async'; -import { initSession, deleteSession, W3C_PREFIXED_CAPS } from '../helpers'; +import {initSession, deleteSession, W3C_PREFIXED_CAPS} from '../helpers'; -function elementTests () { +function elementTests() { describe('element interaction and introspection', function () { let driver; - before (async function () { + before(async function () { driver = await initSession(W3C_PREFIXED_CAPS); chai.use(chaiWebdriverIOAsync(driver)); }); @@ -23,8 +23,9 @@ function elementTests () { await el.should.have.text('test value'); }); it('should not clear an invalid element', async function () { - await (await driver.$('//MockListItem')).clearValue() - .should.eventually.be.rejectedWith(/invalid state/); + await (await driver.$('//MockListItem')) + .clearValue() + .should.eventually.be.rejectedWith(/invalid state/); }); it('should clear an element', async function () { let el = await driver.$('//MockInputField'); @@ -34,8 +35,7 @@ function elementTests () { await el.should.have.text(''); }); it('should not click an invisible element', async function () { - await (await driver.$('#Button1')).click() - .should.eventually.be.rejectedWith(/invalid state/); + await (await driver.$('#Button1')).click().should.eventually.be.rejectedWith(/invalid state/); }); it('should click an element and get its attributes', async function () { let el = await driver.$('#Button2'); @@ -64,11 +64,21 @@ function elementTests () { }); it('should get the rect of an element', async function () { let {elementId} = await driver.$('#nav'); - (await driver.getElementRect(elementId)).should.eql({x: 1, y: 1, width: 100, height: 100}); + (await driver.getElementRect(elementId)).should.eql({ + x: 1, + y: 1, + width: 100, + height: 100, + }); }); it('should get the rect of an element with float vals', async function () { let {elementId} = await driver.$('#lv'); - (await driver.getElementRect(elementId)).should.eql({x: 20.8, y: 15.3, height: 2, width: 30.5}); + (await driver.getElementRect(elementId)).should.eql({ + x: 20.8, + y: 15.3, + height: 2, + width: 30.5, + }); }); it('should determine element equality', async function () { let el1 = await driver.$('#wv'); @@ -83,7 +93,9 @@ function elementTests () { it('should not get the css property of an element when not in a webview', async function () { const {elementId} = await driver.$('#Button1'); - await driver.getElementCSSValue(elementId, 'height').should.eventually.be.rejectedWith({code: 36}); + await driver + .getElementCSSValue(elementId, 'height') + .should.eventually.be.rejectedWith({code: 36}); }); it('should get the css property of an element when in a webview', async function () { await driver.switchContext('WEBVIEW_1'); diff --git a/packages/fake-driver/test/e2e/find-element-tests.js b/packages/fake-driver/test/e2e/find-element-tests.js index 2350d604e..969c9b5be 100644 --- a/packages/fake-driver/test/e2e/find-element-tests.js +++ b/packages/fake-driver/test/e2e/find-element-tests.js @@ -1,11 +1,10 @@ -import { initSession, deleteSession, W3C_PREFIXED_CAPS } from '../helpers'; +import {initSession, deleteSession, W3C_PREFIXED_CAPS} from '../helpers'; import chaiWebdriverIOAsync from 'chai-webdriverio-async'; - -function findElementTests () { +function findElementTests() { describe('finding elements', function () { let driver; - before (async function () { + before(async function () { driver = await initSession(W3C_PREFIXED_CAPS); chai.use(chaiWebdriverIOAsync(driver)); }); diff --git a/packages/fake-driver/test/e2e/general-tests.js b/packages/fake-driver/test/e2e/general-tests.js index ea3d1bcca..d8410abbc 100644 --- a/packages/fake-driver/test/e2e/general-tests.js +++ b/packages/fake-driver/test/e2e/general-tests.js @@ -1,7 +1,7 @@ import chaiWebdriverIOAsync from 'chai-webdriverio-async'; -import { initSession, deleteSession, W3C_PREFIXED_CAPS } from '../helpers'; +import {initSession, deleteSession, W3C_PREFIXED_CAPS} from '../helpers'; -function generalTests () { +function generalTests() { describe('generic actions', function () { let driver; @@ -37,8 +37,9 @@ function generalTests () { (await driver.getOrientation()).should.equal('LANDSCAPE'); }); it('should not set the orientation to something invalid', async function () { - await driver.setOrientation('INSIDEOUT') - .should.eventually.be.rejectedWith(/Orientation must be/); + await driver + .setOrientation('INSIDEOUT') + .should.eventually.be.rejectedWith(/Orientation must be/); }); it('should get a screenshot', async function () { @@ -56,8 +57,9 @@ function generalTests () { await driver.setTimeout({implicit: 1000}); }); it('should not set invalid implicit wait timeout', async function () { - await driver.setTimeout({implicit: 'foo'}) - .should.eventually.be.rejectedWith(/values are not valid/); + await driver + .setTimeout({implicit: 'foo'}) + .should.eventually.be.rejectedWith(/values are not valid/); }); // skip these until basedriver supports these timeouts @@ -65,16 +67,18 @@ function generalTests () { await driver.setTimeout({script: 1000}); }); it.skip('should not set invalid async script timeout', async function () { - await driver.setTimeout({script: 'foo'}) - .should.eventually.be.rejectedWith(/values are not valid/); + await driver + .setTimeout({script: 'foo'}) + .should.eventually.be.rejectedWith(/values are not valid/); }); it.skip('should set page load timeout', async function () { await driver.setTimeout({pageLoad: 1000}); }); it.skip('should not set page load script timeout', async function () { - await driver.setTimeout({pageLoad: 'foo'}) - .should.eventually.be.rejectedWith(/values are not valid/); + await driver + .setTimeout({pageLoad: 'foo'}) + .should.eventually.be.rejectedWith(/values are not valid/); }); it('should allow performing actions that do nothing but save them', async function () { @@ -83,19 +87,19 @@ function generalTests () { type: 'pointer', id: 'finger1', parameters: { - pointerType: 'touch' + pointerType: 'touch', }, actions: [ { type: 'pointerDown', - button: 0 + button: 0, }, { type: 'pointerUp', - button: 0 - } - ] - } + button: 0, + }, + ], + }, ]; await driver.performActions(actions); const [res] = await driver.getLogs('actions'); diff --git a/packages/fake-driver/test/helpers.js b/packages/fake-driver/test/helpers.js index 407aedcac..0f535d4fc 100644 --- a/packages/fake-driver/test/helpers.js +++ b/packages/fake-driver/test/helpers.js @@ -1,5 +1,5 @@ import path from 'path'; -import { remote as wdio } from 'webdriverio'; +import {remote as wdio} from 'webdriverio'; const TEST_HOST = '127.0.0.1'; const TEST_PORT = 4774; @@ -19,7 +19,7 @@ const W3C_PREFIXED_CAPS = { 'appium:app': BASE_CAPS.app, 'appium:address': BASE_CAPS.address, 'appium:port': BASE_CAPS.port, - platformName: BASE_CAPS.platformName + platformName: BASE_CAPS.platformName, }; const W3C_CAPS = { @@ -31,17 +31,27 @@ const WD_OPTS = { hostname: TEST_HOST, port: TEST_PORT, connectionRetryCount: 0, - logLevel: 'error' + logLevel: 'error', }; -async function initSession (w3cPrefixedCaps) { +async function initSession(w3cPrefixedCaps) { return await wdio({...WD_OPTS, capabilities: w3cPrefixedCaps}); } -async function deleteSession (driver) { +async function deleteSession(driver) { try { await driver.deleteSession(); } catch (ign) {} } -export { initSession, deleteSession, TEST_APP, TEST_HOST, TEST_PORT, BASE_CAPS, W3C_CAPS, W3C_PREFIXED_CAPS, WD_OPTS }; +export { + initSession, + deleteSession, + TEST_APP, + TEST_HOST, + TEST_PORT, + BASE_CAPS, + W3C_CAPS, + W3C_PREFIXED_CAPS, + WD_OPTS, +}; diff --git a/packages/fake-driver/test/unit/command.spec.js b/packages/fake-driver/test/unit/command.spec.js index ce0a5c25b..7dc47b327 100644 --- a/packages/fake-driver/test/unit/command.spec.js +++ b/packages/fake-driver/test/unit/command.spec.js @@ -6,23 +6,18 @@ import findCommands from '../../lib/commands/find'; import generalCommands from '../../lib/commands/general'; import exportedCommands from '../../lib/commands'; - - describe('Driver commands', function () { let allCommands = [ _.keys(contextCommands), _.keys(elementCommands), _.keys(findCommands), - _.keys(generalCommands) + _.keys(generalCommands), ]; let totalCommands = _.sum(allCommands.map((c) => c.length)); it('should not overlap between files', function () { _.union(...allCommands).length.should.equal(totalCommands); }); it('should export all commands and not leave any out', function () { - _.difference( - _.union(...allCommands), - _.keys(exportedCommands) - ).should.eql([]); + _.difference(_.union(...allCommands), _.keys(exportedCommands)).should.eql([]); }); }); diff --git a/packages/fake-driver/test/unit/driver.spec.js b/packages/fake-driver/test/unit/driver.spec.js index 4b881bd7f..c87a3b36e 100644 --- a/packages/fake-driver/test/unit/driver.spec.js +++ b/packages/fake-driver/test/unit/driver.spec.js @@ -1,10 +1,9 @@ // transpile:mocha import _ from 'lodash'; -import { FakeDriver } from '../../lib'; -import { W3C_CAPS, W3C_PREFIXED_CAPS } from '../helpers'; -import { baseDriverUnitTests } from '@appium/base-driver/build/test/basedriver'; - +import {FakeDriver} from '../../lib'; +import {W3C_CAPS, W3C_PREFIXED_CAPS} from '../helpers'; +import {baseDriverUnitTests} from '@appium/base-driver/build/test/basedriver'; // test the same things as for base driver baseDriverUnitTests(FakeDriver, _.cloneDeep(W3C_PREFIXED_CAPS)); @@ -13,7 +12,10 @@ describe('FakeDriver', function () { it('should not start a session when a unique session is already running', async function () { let d1 = new FakeDriver(); let [uniqueSession] = await d1.createSession(null, null, { - alwaysMatch: { ..._.cloneDeep(W3C_PREFIXED_CAPS), 'appium:uniqueApp': true }, + alwaysMatch: { + ..._.cloneDeep(W3C_PREFIXED_CAPS), + 'appium:uniqueApp': true, + }, firstMatch: [{}], }); uniqueSession.should.be.a('string'); @@ -21,12 +23,7 @@ describe('FakeDriver', function () { let otherSessionData = [d1.driverData]; try { await d2 - .createSession( - null, - null, - _.cloneDeep(W3C_CAPS), - otherSessionData, - ) + .createSession(null, null, _.cloneDeep(W3C_CAPS), otherSessionData) .should.eventually.be.rejectedWith(/unique/); } finally { await d1.deleteSession(uniqueSession); diff --git a/packages/fake-plugin/lib/logger.js b/packages/fake-plugin/lib/logger.js index 8c8614239..3c816e0eb 100644 --- a/packages/fake-plugin/lib/logger.js +++ b/packages/fake-plugin/lib/logger.js @@ -1,5 +1,4 @@ -import { logger } from '@appium/support'; - +import {logger} from '@appium/support'; const log = logger.getLogger('FakePlugin'); diff --git a/packages/fake-plugin/lib/plugin.js b/packages/fake-plugin/lib/plugin.js index 82daed660..d5813a620 100644 --- a/packages/fake-plugin/lib/plugin.js +++ b/packages/fake-plugin/lib/plugin.js @@ -3,13 +3,12 @@ import BasePlugin from '@appium/base-plugin'; import B from 'bluebird'; - export default class FakePlugin extends BasePlugin { - constructor (pluginName, opts = {}) { + constructor(pluginName, opts = {}) { super(pluginName, opts); } - async getFakePluginArgs () { + async getFakePluginArgs() { await B.delay(1); return this.cliArgs; } @@ -17,35 +16,39 @@ export default class FakePlugin extends BasePlugin { static newMethodMap = { '/session/:sessionId/fake_data': { GET: {command: 'getFakeSessionData', neverProxy: true}, - POST: {command: 'setFakeSessionData', payloadParams: {required: ['data']}, neverProxy: true} + POST: { + command: 'setFakeSessionData', + payloadParams: {required: ['data']}, + neverProxy: true, + }, }, '/session/:sessionId/fakepluginargs': { - GET: {command: 'getFakePluginArgs', neverProxy: true} + GET: {command: 'getFakePluginArgs', neverProxy: true}, }, }; static _unexpectedData = null; - static fakeRoute (req, res) { + static fakeRoute(req, res) { res.send(JSON.stringify({fake: 'fakeResponse'})); } - static unexpectedData (req, res) { + static unexpectedData(req, res) { res.send(JSON.stringify(FakePlugin._unexpectedData)); FakePlugin._unexpectedData = null; } - - static async updateServer (expressApp/*, httpServer*/) { // eslint-disable-line require-await + // eslint-disable-next-line require-await + static async updateServer(expressApp /*, httpServer*/) { expressApp.all('/fake', FakePlugin.fakeRoute); expressApp.all('/unexpected', FakePlugin.unexpectedData); } - async getPageSource (next, driver, ...args) { + async getPageSource(next, driver, ...args) { await B.delay(10); return `${JSON.stringify(args)}`; } - async findElement (next, driver, ...args) { + async findElement(next, driver, ...args) { this.logger.info(`Before findElement is run with args ${JSON.stringify(args)}`); const originalRes = await next(); this.logger.info(`After findElement is run`); @@ -53,25 +56,25 @@ export default class FakePlugin extends BasePlugin { return originalRes; } - async getFakeSessionData (next, driver) { + async getFakeSessionData(next, driver) { await B.delay(1); return driver.fakeSessionData || null; } - async setFakeSessionData (next, driver, ...args) { + async setFakeSessionData(next, driver, ...args) { await B.delay(1); driver.fakeSessionData = args[0]; return null; } - async getWindowHandle (next) { + async getWindowHandle(next) { const handle = await next(); return `<<${handle}>>`; } - onUnexpectedShutdown (driver, cause) { + onUnexpectedShutdown(driver, cause) { FakePlugin._unexpectedData = `Session ended because ${cause}`; } } -export { FakePlugin }; +export {FakePlugin}; diff --git a/packages/fake-plugin/test/unit/plugin.spec.js b/packages/fake-plugin/test/unit/plugin.spec.js index b78ef1186..e211f9583 100644 --- a/packages/fake-plugin/test/unit/plugin.spec.js +++ b/packages/fake-plugin/test/unit/plugin.spec.js @@ -2,15 +2,15 @@ import FakePlugin from '../../lib/plugin'; import B from 'bluebird'; class FakeExpress { - constructor () { + constructor() { this.routes = {}; } - all (route, handler) { + all(route, handler) { this.routes[route] = handler; } - async get (route) { + async get(route) { return await new B((resolve, reject) => { try { const res = { @@ -38,17 +38,19 @@ describe('fake plugin', function () { it('should wrap find element', async function () { const p = new FakePlugin('fake'); - await p.findElement(() => ({el: 'fakeEl'}), {}, 'arg1', 'arg2').should.eventually.eql({ - el: 'fakeEl', - fake: true, - }); + await p + .findElement(() => ({el: 'fakeEl'}), {}, 'arg1', 'arg2') + .should.eventually.eql({ + el: 'fakeEl', + fake: true, + }); }); it('should handle page source', async function () { const p = new FakePlugin('fake'); - await p.getPageSource(() => {}, {}, 'arg1', 'arg2').should.eventually.eql( - '["arg1","arg2"]' - ); + await p + .getPageSource(() => {}, {}, 'arg1', 'arg2') + .should.eventually.eql('["arg1","arg2"]'); }); it('should handle getFakeSessionData', async function () { diff --git a/packages/gulp-plugins/gulpfile.js b/packages/gulp-plugins/gulpfile.js index c054490d9..3e3f08bf1 100644 --- a/packages/gulp-plugins/gulpfile.js +++ b/packages/gulp-plugins/gulpfile.js @@ -2,7 +2,6 @@ const boilerplate = require('./index').boilerplate.use(require('gulp')); - require('./test/gulpfile-js'); boilerplate({ @@ -12,10 +11,15 @@ boilerplate({ files: ['${testDir}/**/*-specs.js', '!${testDir}/fixtures', '!${testDir}/**/*-e2e-specs.js'], }, coverage: { - files: ['./build/test/**/*-specs.js', '!./build/test/fixtures', '!./build/test/**/*-e2e-specs.js', '!./build/test/generated'], + files: [ + './build/test/**/*-specs.js', + '!./build/test/fixtures', + '!./build/test/**/*-e2e-specs.js', + '!./build/test/generated', + ], verbose: true, }, build: 'Appium Gulp Plugins', extraDefaultTasks: ['e2e-test', 'test-transpile-lots-of-files'], - testReporter: 'dot' + testReporter: 'dot', }); diff --git a/packages/gulp-plugins/lib/boilerplate.js b/packages/gulp-plugins/lib/boilerplate.js index 2c8180872..061672de7 100644 --- a/packages/gulp-plugins/lib/boilerplate.js +++ b/packages/gulp-plugins/lib/boilerplate.js @@ -4,7 +4,6 @@ const _ = require('lodash'); const tasks = require('./tasks/'); const log = require('fancy-log'); - if (process.env.TRAVIS || process.env.CI) { process.env.REAL_DEVICE = 0; } @@ -48,7 +47,15 @@ const DEFAULT_OPTS = { }, yamllint: true, yaml: { - files: ['**/.*.yml', '**/*.yml', '**/.*.yaml', '**/*.yaml', '!test/**', '!node_modules/**', '!**/node_modules/**'], + files: [ + '**/.*.yml', + '**/*.yml', + '**/.*.yaml', + '**/*.yaml', + '!test/**', + '!node_modules/**', + '!**/node_modules/**', + ], safe: false, }, }; @@ -104,9 +111,9 @@ const boilerplate = function (gulp, opts) { } const watchSequence = opts.lintOnWatch ? defaultSequence - : defaultSequence.filter(function filterLintTasks (step) { - return step !== 'lint'; - }); + : defaultSequence.filter(function filterLintTasks(step) { + return step !== 'lint'; + }); spawnWatcher.configure('watch', opts.files, watchSequence); } @@ -115,12 +122,11 @@ const boilerplate = function (gulp, opts) { gulp.task('default', gulp.series(opts.watch ? 'watch' : 'once')); }; - module.exports = { DEFAULTS: _.cloneDeep(DEFAULT_OPTS), - use (gulp) { - return function callBoilerplate (opts) { + use(gulp) { + return function callBoilerplate(opts) { boilerplate(gulp, opts); }; - } + }, }; diff --git a/packages/gulp-plugins/lib/sourcemaps.js b/packages/gulp-plugins/lib/sourcemaps.js index cd60ebd0c..20add9703 100644 --- a/packages/gulp-plugins/lib/sourcemaps.js +++ b/packages/gulp-plugins/lib/sourcemaps.js @@ -3,11 +3,10 @@ const path = require('path'); const sourcemaps = require('gulp-sourcemaps'); const replace = require('gulp-replace'); -const { EOL } = require('os'); - +const {EOL} = require('os'); const SOURCEMAP_OPTS = { - sourceRoot (file) { + sourceRoot(file) { // Point to source root relative to the transpiled file return path.relative(path.join(file.cwd, file.path), file.base); }, @@ -16,8 +15,7 @@ const SOURCEMAP_OPTS = { const HEADER = `require('source-map-support').install();${EOL + EOL}`; - -module.exports = function getSourceMapFns (opts = {}) { +module.exports = function getSourceMapFns(opts = {}) { const sourceMapOpts = Object.assign({}, SOURCEMAP_OPTS, opts); return { diff --git a/packages/gulp-plugins/lib/spawn-watcher.js b/packages/gulp-plugins/lib/spawn-watcher.js index 9e42a68a6..390ee3906 100644 --- a/packages/gulp-plugins/lib/spawn-watcher.js +++ b/packages/gulp-plugins/lib/spawn-watcher.js @@ -5,11 +5,10 @@ const red = require('ansi-red'); const notifier = require('node-notifier'); const moment = require('moment'); - const COLOR_CODE_REGEXP = /\u001b\[(\d+(;\d+)*)?m/g; // eslint-disable-line no-control-regex module.exports = { - use (gulp, opts = {}) { + use(gulp, opts = {}) { this.gulp = gulp; this.title = opts.build || 'Appium'; @@ -19,7 +18,7 @@ module.exports = { return this; }, - notify (subtitle, message) { + notify(subtitle, message) { if (process.argv.includes('--no-notif')) { return; } @@ -35,11 +34,11 @@ module.exports = { } }, - notifyOK () { + notifyOK() { this.notify('Build success!', 'All Good!'); }, - handleError (err) { + handleError(err) { this.errored = true; // log the error @@ -56,7 +55,7 @@ module.exports = { } }, - configure (taskName, filePattern, sequence) { + configure(taskName, filePattern, sequence) { const notifyWatch = (done) => { if (!this.errored) { this.notifyOK(); @@ -67,10 +66,14 @@ module.exports = { this.gulp.task(taskName, () => { this.exitOnError = false; - return this.gulp.watch(filePattern, { - ignoreInitial: false, - ignored: '**/gulpfile.js' - }, this.gulp.series(sequence, notifyWatch)); + return this.gulp.watch( + filePattern, + { + ignoreInitial: false, + ignored: '**/gulpfile.js', + }, + this.gulp.series(sequence, notifyWatch) + ); }); - } + }, }; diff --git a/packages/gulp-plugins/lib/stream-combiner.js b/packages/gulp-plugins/lib/stream-combiner.js index 33f4ffb5a..999f6f3e6 100644 --- a/packages/gulp-plugins/lib/stream-combiner.js +++ b/packages/gulp-plugins/lib/stream-combiner.js @@ -3,16 +3,15 @@ const through = require('through2'); const EE = require('events').EventEmitter; - -module.exports = function combine (pipeFn) { +module.exports = function combine(pipeFn) { const inStream = through.obj(); const outStream = pipeFn(inStream); const combinedStream = new EE(); // not a real stream, just pretending - combinedStream.on('pipe', function onPipe (source) { + combinedStream.on('pipe', function onPipe(source) { source.unpipe(this); source.pipe(inStream); }); - combinedStream.pipe = function pipeFn (dest, options) { + combinedStream.pipe = function pipeFn(dest, options) { return outStream.pipe(dest, options); }; return combinedStream; diff --git a/packages/gulp-plugins/lib/tasks/ci.js b/packages/gulp-plugins/lib/tasks/ci.js index 7e5d03a90..a69ffe133 100644 --- a/packages/gulp-plugins/lib/tasks/ci.js +++ b/packages/gulp-plugins/lib/tasks/ci.js @@ -7,7 +7,7 @@ const {sync: findRoot} = require('pkg-dir'); const axios = require('axios'); const B = require('bluebird'); const os = require('os'); -const { Octokit } = require('@octokit/rest'); +const {Octokit} = require('@octokit/rest'); const _ = require('lodash'); const FormData = require('form-data'); @@ -24,7 +24,7 @@ const OUTPUT_INTERVAL = 60000; const MOCHA_PARALLEL_TEST_BROKEN_LINE = `if (value.type === 'test') {`; const MOCHA_PARALLEL_TEST_FIXED_LINE = `if (value.type === 'test') {\n delete value.fn;`; -const configure = function configure (gulp, opts) { +const configure = function configure(gulp, opts) { const owner = opts.ci.owner || GITHUB_OWNER; const repo = opts.ci.repo || GITHUB_REPO; @@ -34,15 +34,25 @@ const configure = function configure (gulp, opts) { * `mocha-parallel-tests` is broken at the moment, so skipped describe blocks fail * the fix is simple, and this patches the error until they fix the package **/ - gulp.task('fix-mocha-parallel-tests', async function fixMochaParallelTests () { + gulp.task('fix-mocha-parallel-tests', async function fixMochaParallelTests() { log(`Updating 'mocha-parallel-tests'`); - const filePath = path.resolve(root, 'node_modules', 'mocha-parallel-tests', 'dist', 'main', 'util.js'); + const filePath = path.resolve( + root, + 'node_modules', + 'mocha-parallel-tests', + 'dist', + 'main', + 'util.js' + ); log(`File: '${filePath}'`); try { let script = await readFile(filePath, {encoding: 'utf8'}); - script = await script.replace(MOCHA_PARALLEL_TEST_BROKEN_LINE, MOCHA_PARALLEL_TEST_FIXED_LINE); + script = await script.replace( + MOCHA_PARALLEL_TEST_BROKEN_LINE, + MOCHA_PARALLEL_TEST_FIXED_LINE + ); await writeFile(filePath, script); } catch (err) { const msg = err.message.includes('ENOENT') @@ -52,7 +62,7 @@ const configure = function configure (gulp, opts) { } }); - gulp.task('github:upload', async function githubUpload () { + gulp.task('github:upload', async function githubUpload() { const githubToken = process.env.GITHUB_TOKEN; if (_.isEmpty(githubToken)) { log.warn('No GitHub token found in GITHUB_TOKEN environment variable'); @@ -114,7 +124,7 @@ const configure = function configure (gulp, opts) { gulp.task('github-upload', gulp.series(['github:upload'])); - gulp.task('github:download', async function githubDownload () { + gulp.task('github:download', async function githubDownload() { const githubToken = process.env.GITHUB_TOKEN; if (_.isEmpty(githubToken)) { log.warn('No GitHub token found in GITHUB_TOKEN environment variable'); @@ -134,10 +144,12 @@ const configure = function configure (gulp, opts) { log.info(`Downloading asset from '${asset.browser_download_url}'`); const url = asset.browser_download_url; const writer = fs.createWriteStream(`${tempDir}/appium.zip`); - const responseStream = (await axios({ - url, - responseType: 'stream', - })).data; + const responseStream = ( + await axios({ + url, + responseType: 'stream', + }) + ).data; responseStream.pipe(writer); return await new B((resolve, reject) => { @@ -153,7 +165,7 @@ const configure = function configure (gulp, opts) { throw new Error(`Unable to find Appium build asset`); }); - gulp.task('saucelabs:upload', async function sauceLabsUpload () { + gulp.task('saucelabs:upload', async function sauceLabsUpload() { // Find the latest bundle log.info('Uploading to Sauce Storage'); const tempDir = os.tmpdir(); @@ -178,7 +190,6 @@ const configure = function configure (gulp, opts) { gulp.task('sauce-storage-upload', gulp.series(['github:download', 'saucelabs:upload'])); - /** * This task is meant to be backgrounded, and killed using OS tools, so it * never finishes. @@ -189,10 +200,10 @@ const configure = function configure (gulp, opts) { * $ kill 38060 * [1] + 38060 terminated $(npm bin)/gulp periodic-output */ - gulp.task('periodic-output', function periodicOutput () { + gulp.task('periodic-output', function periodicOutput() { const interval = opts.ci.interval || OUTPUT_INTERVAL; - return new B(function writeToStdout () { - setInterval(function print () { + return new B(function writeToStdout() { + setInterval(function print() { process.stdout.write('.'); }, interval); }); diff --git a/packages/gulp-plugins/lib/tasks/clean.js b/packages/gulp-plugins/lib/tasks/clean.js index 3c14a47c1..9846da2e0 100644 --- a/packages/gulp-plugins/lib/tasks/clean.js +++ b/packages/gulp-plugins/lib/tasks/clean.js @@ -5,11 +5,10 @@ const vinylPaths = require('vinyl-paths'); const del = require('del'); const debug = require('gulp-debug'); const gulpIf = require('gulp-if'); -const { isVerbose } = require('../utils'); +const {isVerbose} = require('../utils'); - -const configure = function configure (gulp, opts) { - gulp.task('clean', function clean () { +const configure = function configure(gulp, opts) { + gulp.task('clean', function clean() { if (opts.transpile) { return gulp .src(opts.transpileOut, { diff --git a/packages/gulp-plugins/lib/tasks/coverage.js b/packages/gulp-plugins/lib/tasks/coverage.js index a1136da20..019caa050 100644 --- a/packages/gulp-plugins/lib/tasks/coverage.js +++ b/packages/gulp-plugins/lib/tasks/coverage.js @@ -10,10 +10,9 @@ const path = require('path'); const utils = require('../utils'); const log = require('fancy-log'); - -const configure = function configure (gulp, opts, env) { +const configure = function configure(gulp, opts, env) { let npmBin; - gulp.task('npm-bin', async function getNpmBin () { + gulp.task('npm-bin', async function getNpmBin() { if (npmBin) { return B.resolve(); } @@ -25,12 +24,12 @@ const configure = function configure (gulp, opts, env) { log(`Determined npm bin: ${npmBin}`); }); - const doCoverage = function doCoverage (taskName, filePatterns, targetDir) { + const doCoverage = function doCoverage(taskName, filePatterns, targetDir) { const subTaskName = `${taskName}:run`; const covTestFiles = utils.translatePaths([filePatterns], env.fileAliases); - gulp.task(subTaskName, async function doSubTask () { + gulp.task(subTaskName, async function doSubTask() { const files = await globby(covTestFiles); - const bins = ['nyc', '_mocha'].reduce(function getFullPaths (bins, item) { + const bins = ['nyc', '_mocha'].reduce(function getFullPaths(bins, item) { bins[item] = path.resolve(npmBin, item); return bins; }, {}); @@ -50,19 +49,19 @@ const configure = function configure (gulp, opts, env) { env._TESTING = 1; env.NODE_ENV = 'coverage'; log(`Running command: ${bins.nyc} ${args.join(' ')}`); - return new B(function runCmd (resolve, reject) { + return new B(function runCmd(resolve, reject) { const proc = spawn(bins.nyc, args, { env, stdio: opts.coverage.verbose ? 'inherit' : 'ignore', }); - proc.on('close', function onClose (code) { + proc.on('close', function onClose(code) { if (code === 0) { resolve(); } else { reject(new Error(`Coverage command exit code: ${code}`)); } }); - proc.on('error', function onError (err) { + proc.on('error', function onError(err) { reject(new Error(`Coverage error: ${err}`)); }); }); @@ -72,12 +71,15 @@ const configure = function configure (gulp, opts, env) { if (opts.coverage) { doCoverage('coverage', opts.coverage.files, 'coverage'); - ['coveralls:run', 'coveralls'] - .map((taskName) => gulp.task(taskName, function reportDeprecatedCoveralls () { - log(`Coveralls integration has been removed as per ` + - `https://github.com/appium/appium/issues/14648. ` + - `Nothing will be done in scope of '${taskName}' task`); - })); + ['coveralls:run', 'coveralls'].map((taskName) => + gulp.task(taskName, function reportDeprecatedCoveralls() { + log( + `Coveralls integration has been removed as per ` + + `https://github.com/appium/appium/issues/14648. ` + + `Nothing will be done in scope of '${taskName}' task` + ); + }) + ); } if (opts['coverage-e2e']) { doCoverage('coverage-e2e', opts['coverage-e2e'].files, 'coverage-e2e'); diff --git a/packages/gulp-plugins/lib/tasks/e2e-test.js b/packages/gulp-plugins/lib/tasks/e2e-test.js index c701e4284..047bcf736 100644 --- a/packages/gulp-plugins/lib/tasks/e2e-test.js +++ b/packages/gulp-plugins/lib/tasks/e2e-test.js @@ -8,16 +8,18 @@ const gulpIf = require('gulp-if'); const log = require('fancy-log'); const utils = require('../utils'); +const {isVerbose} = utils; -const { isVerbose } = utils; - -const configure = function configure (gulp, opts, env) { - const e2eTestFiles = utils.translatePaths([opts.e2eTest.files || opts.e2eTestFiles], env.fileAliases); - gulp.task('e2e-test:run', async function e2eTestRun () { +const configure = function configure(gulp, opts, env) { + const e2eTestFiles = utils.translatePaths( + [opts.e2eTest.files || opts.e2eTestFiles], + env.fileAliases + ); + gulp.task('e2e-test:run', async function e2eTestRun() { const mochaOpts = { reporter: utils.getTestReporter(opts), timeout: opts.testTimeout, - 'require': opts.testRequire || [], + require: opts.testRequire || [], exit: true, color: true, traceWarnings: opts.e2eTest.traceWarnings, @@ -27,15 +29,15 @@ const configure = function configure (gulp, opts, env) { process.env._TESTING = 1; const mochaCmd = function () { - return new B(function runCmd (resolve, reject) { + return new B(function runCmd(resolve, reject) { gulp .src(e2eTestFiles, {read: true, allowEmpty: true}) .pipe(gulpIf(isVerbose(), debug())) .pipe(mocha(mochaOpts)) - .on('error', function onError (err) { + .on('error', function onError(err) { reject(err); }) - .once('_result', function onResult (...args) { + .once('_result', function onResult(...args) { resolve(...args); }); }); diff --git a/packages/gulp-plugins/lib/tasks/gradle.js b/packages/gulp-plugins/lib/tasks/gradle.js index 500c6cfac..902a3ef9a 100644 --- a/packages/gulp-plugins/lib/tasks/gradle.js +++ b/packages/gulp-plugins/lib/tasks/gradle.js @@ -6,16 +6,15 @@ const log = require('fancy-log'); const semver = require('semver'); const globby = require('globby'); - -function logFileChanges (changes = []) { +function logFileChanges(changes = []) { // `changes` will have entries like // { file: "app/build.gradle", hasChanged: true } changes = changes.filter((entry) => entry.hasChanged).map((entry) => entry.file); log(`Updated files: ${changes.join(', ')}`); } -const configure = function configure (gulp) { - gulp.task('gradle-version-update', async function gradleVersionUpdate () { +const configure = function configure(gulp) { + gulp.task('gradle-version-update', async function gradleVersionUpdate() { const files = await globby(['app/build.gradle']); if (!files.length) { throw new Error('No app/build.gradle file found'); @@ -27,7 +26,9 @@ const configure = function configure (gulp) { throw new Error('No package version argument (use `--package-version=xxx`)'); } if (!semver.valid(version)) { - throw new Error(`Invalid version specified '${version}'. Version should be in the form '1.2.3'`); + throw new Error( + `Invalid version specified '${version}'. Version should be in the form '1.2.3'` + ); } let changedFiles = await replace({ diff --git a/packages/gulp-plugins/lib/tasks/index.js b/packages/gulp-plugins/lib/tasks/index.js index 463705de8..96561e1fc 100644 --- a/packages/gulp-plugins/lib/tasks/index.js +++ b/packages/gulp-plugins/lib/tasks/index.js @@ -12,7 +12,7 @@ const ci = require('./ci'); const iosApps = require('./ios-apps'); const prepublish = require('./prepublish'); -const configure = function configure (gulp, opts, env) { +const configure = function configure(gulp, opts, env) { clean.configure(gulp, opts); transpile.configure(gulp, opts, env); diff --git a/packages/gulp-plugins/lib/tasks/ios-apps.js b/packages/gulp-plugins/lib/tasks/ios-apps.js index 113ae2997..d2fe758af 100644 --- a/packages/gulp-plugins/lib/tasks/ios-apps.js +++ b/packages/gulp-plugins/lib/tasks/ios-apps.js @@ -3,19 +3,18 @@ const path = require('path'); const log = require('fancy-log'); const _ = require('lodash'); -const { exec } = require('../utils'); +const {exec} = require('../utils'); const B = require('bluebird'); const fs = require('fs'); const rimraf = B.promisify(require('rimraf')); - const renameFile = B.promisify(fs.rename, {context: fs}); const MAX_BUFFER_SIZE = 524288; const REAL_DEVICE_FLAGS = ['IOS_REAL_DEVICE', 'REAL_DEVICE']; -function configure (gulp, opts) { +function configure(gulp, opts) { if (_.isEmpty(opts.iosApps)) { // nothing to do return; @@ -29,20 +28,20 @@ function configure (gulp, opts) { iphonesimulator: { name: 'iphonesimulator', buildPath: path.resolve('build', 'Release-iphonesimulator', opts.iosApps.appName), - finalPath: relativeLocations.iphonesimulator + finalPath: relativeLocations.iphonesimulator, }, iphoneos: { name: 'iphoneos', buildPath: path.resolve('build', 'Release-iphoneos', opts.iosApps.appName), - finalPath: relativeLocations.iphoneos - } + finalPath: relativeLocations.iphoneos, + }, }; // the sdks against which we will build let sdks = ['iphonesimulator']; let sdkVer; - async function getIOSSDK () { + async function getIOSSDK() { if (!sdkVer) { try { const {stdout} = await exec('xcrun', ['--sdk', 'iphonesimulator', '--show-sdk-version']); @@ -55,14 +54,14 @@ function configure (gulp, opts) { return sdkVer; } - function logErrorLines (str = '') { + function logErrorLines(str = '') { str = `${str}`; for (const line of str.split('\n')) { log.error(` ${line}`); } } - function logError (err, prefix = 'Failed:') { + function logError(err, prefix = 'Failed:') { log.error(`${prefix}: ${err.message}`); log.error('Stdout:'); logErrorLines(err.stdout); @@ -70,7 +69,7 @@ function configure (gulp, opts) { logErrorLines(err.stderr); } - async function cleanApp (appRoot, sdk) { + async function cleanApp(appRoot, sdk) { log(`Cleaning app for ${sdk} at app root '${appRoot}'`); try { const cmd = 'xcodebuild'; @@ -83,12 +82,14 @@ function configure (gulp, opts) { } } - gulp.task('ios-apps:sdks', function findSDKs (done) { + gulp.task('ios-apps:sdks', function findSDKs(done) { // determine if the real device sdk should be used, too for (const flag of REAL_DEVICE_FLAGS) { const value = process.env[flag]; if (!_.isEmpty(value) && !!parseInt(value, 10)) { - log(`Enabling real device build because '${flag}' environment variable set (value is '${value}')`); + log( + `Enabling real device build because '${flag}' environment variable set (value is '${value}')` + ); sdks.push('iphoneos'); } } @@ -96,7 +97,7 @@ function configure (gulp, opts) { done(); }); - gulp.task('ios-apps:clean', async function cleanAll () { + gulp.task('ios-apps:clean', async function cleanAll() { log('Cleaning all sdks'); const sdkVer = await getIOSSDK(); for (const sdk of sdks) { @@ -116,7 +117,7 @@ function configure (gulp, opts) { } }); - async function buildApp (appRoot, sdk) { + async function buildApp(appRoot, sdk) { log(`Building app for ${sdk} at app root '${appRoot}'`); try { const cmd = 'xcodebuild'; @@ -133,7 +134,7 @@ function configure (gulp, opts) { } } - gulp.task('ios-apps:build', async function buildAll () { + gulp.task('ios-apps:build', async function buildAll() { log('Building all apps'); const sdkVer = await getIOSSDK(); for (const sdk of sdks) { @@ -141,7 +142,7 @@ function configure (gulp, opts) { } }); - gulp.task('ios-apps:rename', async function iosAppsRename () { + gulp.task('ios-apps:rename', async function iosAppsRename() { log('Renaming apps'); for (const sdk of sdks) { log(` Renaming for ${sdk}`); @@ -150,7 +151,10 @@ function configure (gulp, opts) { } }); - gulp.task('ios-apps:install', gulp.series('ios-apps:sdks', 'ios-apps:clean', 'ios-apps:build', 'ios-apps:rename')); + gulp.task( + 'ios-apps:install', + gulp.series('ios-apps:sdks', 'ios-apps:clean', 'ios-apps:build', 'ios-apps:rename') + ); } module.exports = { diff --git a/packages/gulp-plugins/lib/tasks/lint.js b/packages/gulp-plugins/lib/tasks/lint.js index 9ef091118..2df46e205 100644 --- a/packages/gulp-plugins/lib/tasks/lint.js +++ b/packages/gulp-plugins/lib/tasks/lint.js @@ -3,14 +3,13 @@ const eslint = require('gulp-eslint'); const debug = require('gulp-debug'); const gulpIf = require('gulp-if'); -const { isVerbose } = require('../utils'); +const {isVerbose} = require('../utils'); const yamlLint = require('../yaml-lint'); - -const configure = function configure (gulp, opts) { +const configure = function configure(gulp, opts) { const verbose = isVerbose(); - gulp.task('eslint', function eslintTask () { + gulp.task('eslint', function eslintTask() { let opts = { fix: process.argv.includes('--fix'), }; @@ -31,14 +30,11 @@ const configure = function configure (gulp, opts) { .pipe(gulpIf((file) => file.eslint && file.eslint.fixed, gulp.dest(process.cwd()))); }); - gulp.task('yamllint', function yamllintTask () { + gulp.task('yamllint', function yamllintTask() { const yamlOpts = { safe: !!opts.yaml.safe, }; - return gulp - .src(opts.yaml.files) - .pipe(gulpIf(verbose, debug())) - .pipe(yamlLint(yamlOpts)); + return gulp.src(opts.yaml.files).pipe(gulpIf(verbose, debug())).pipe(yamlLint(yamlOpts)); }); const lintTasks = []; diff --git a/packages/gulp-plugins/lib/tasks/prepublish.js b/packages/gulp-plugins/lib/tasks/prepublish.js index e17b055b5..7b3ed0a86 100644 --- a/packages/gulp-plugins/lib/tasks/prepublish.js +++ b/packages/gulp-plugins/lib/tasks/prepublish.js @@ -1,7 +1,6 @@ 'use strict'; - -const configure = function configure (gulp, opts) { +const configure = function configure(gulp, opts) { gulp.task('prepublish', gulp.series('clean', 'transpile', opts.extraPrepublishTasks || [])); }; diff --git a/packages/gulp-plugins/lib/tasks/test.js b/packages/gulp-plugins/lib/tasks/test.js index be29a6516..e0d007387 100644 --- a/packages/gulp-plugins/lib/tasks/test.js +++ b/packages/gulp-plugins/lib/tasks/test.js @@ -3,11 +3,10 @@ const e2eTest = require('./e2e-test'); const unitTest = require('./unit-test'); - -const configure = function configure (gulp, opts, env) { +const configure = function configure(gulp, opts, env) { const testEnv = { testDeps: opts.transpile ? ['transpile'] : [], - ...env + ...env, }; let testTasks = []; diff --git a/packages/gulp-plugins/lib/tasks/transpile.js b/packages/gulp-plugins/lib/tasks/transpile.js index b2e3f2836..093053553 100644 --- a/packages/gulp-plugins/lib/tasks/transpile.js +++ b/packages/gulp-plugins/lib/tasks/transpile.js @@ -4,29 +4,26 @@ const Transpiler = require('../../index').Transpiler; const debug = require('gulp-debug'); const gulpIf = require('gulp-if'); const merge = require('merge-stream'); -const { isVerbose } = require('../utils'); +const {isVerbose} = require('../utils'); -const JSON_SOURCES = [ - 'lib/**/*.json', - 'test/**/*.json', -]; +const JSON_SOURCES = ['lib/**/*.json', 'test/**/*.json']; /** * @param {import('gulp').Gulp} gulp - The gulp instance. */ -const configure = function configure (gulp, opts, env) { +const configure = function configure(gulp, opts, env) { const tasks = [ - function transpileSources () { + function transpileSources() { // json files can be copied as is, they don't need to be transpiled - const firstPath = gulp.src(JSON_SOURCES, {base: './'}) - .pipe(gulp.dest(opts.transpileOut)); - const secondPath = gulp.src(opts.files, {base: './'}) + const firstPath = gulp.src(JSON_SOURCES, {base: './'}).pipe(gulp.dest(opts.transpileOut)); + const secondPath = gulp + .src(opts.files, {base: './'}) .pipe(gulpIf(isVerbose(), debug())) .pipe(new Transpiler(opts).stream()) .on('error', env.spawnWatcher.handleError.bind(env.spawnWatcher)) .pipe(gulp.dest(opts.transpileOut)); return merge(firstPath, secondPath); - } + }, ]; if (opts.postTranspile && opts.postTranspile.length) { diff --git a/packages/gulp-plugins/lib/tasks/unit-test.js b/packages/gulp-plugins/lib/tasks/unit-test.js index a11c7d43c..a0ff25e50 100644 --- a/packages/gulp-plugins/lib/tasks/unit-test.js +++ b/packages/gulp-plugins/lib/tasks/unit-test.js @@ -5,17 +5,16 @@ const debug = require('gulp-debug'); const gulpIf = require('gulp-if'); const utils = require('../utils'); - -const configure = function configure (gulp, opts, env) { +const configure = function configure(gulp, opts, env) { const testFiles = utils.translatePaths([opts.test.files || opts.testFiles], env.fileAliases); - gulp.task('unit-test:run', function unitTestRun () { + gulp.task('unit-test:run', function unitTestRun() { const mochaOpts = { reporter: utils.getTestReporter(opts), timeout: opts.testTimeout, traceWarnings: opts.test.traceWarnings, traceDeprecation: opts.test.traceWarnings, color: true, - exit: Boolean(opts.test.exit) + exit: Boolean(opts.test.exit), }; // set env so our code knows when it's being run in a test env process.env._TESTING = 1; diff --git a/packages/gulp-plugins/lib/transpiler.js b/packages/gulp-plugins/lib/transpiler.js index ac8914359..49fa076e0 100644 --- a/packages/gulp-plugins/lib/transpiler.js +++ b/packages/gulp-plugins/lib/transpiler.js @@ -6,22 +6,21 @@ const streamCombiner = require('./stream-combiner'); const path = require('path'); const sourcemaps = require('./sourcemaps'); - const BABEL_OPTS = { configFile: path.resolve(__dirname, '..', '.babelrc'), }; const renameEsX = function () { - return rename(function renameEs (path) { + return rename(function renameEs(path) { path.basename = path.basename.replace(/\.es[67]$/, ''); }); }; -module.exports = function transpiler (opts = {}) { +module.exports = function transpiler(opts = {}) { const {sourceMapInit, sourceMapHeader, sourceMapWrite} = sourcemaps(opts.sourceMapOpts); - this.stream = function stream () { - return streamCombiner(function combine (source) { + this.stream = function stream() { + return streamCombiner(function combine(source) { return source .pipe(sourceMapInit) .pipe(babel(Object.assign({}, BABEL_OPTS, opts.babelOpts))) diff --git a/packages/gulp-plugins/lib/utils.js b/packages/gulp-plugins/lib/utils.js index 1fffdb975..5bac87e77 100644 --- a/packages/gulp-plugins/lib/utils.js +++ b/packages/gulp-plugins/lib/utils.js @@ -1,39 +1,39 @@ 'use strict'; const _ = require('lodash'); -const { exec } = require('child_process'); +const {exec} = require('child_process'); const B = require('bluebird'); -const { quote } = require('shell-quote'); - +const {quote} = require('shell-quote'); // string interpolation -const interpolate = function interpolate (s, opts) { - return _.keys(opts).reduce(function replace (s, k) { +const interpolate = function interpolate(s, opts) { + return _.keys(opts).reduce(function replace(s, k) { return s.replace(new RegExp(`\\$\\{\\s*${k}\\s*\\}`, 'g'), opts[k]); }, s); }; -const translatePaths = function translatePaths (files, fileAliases) { +const translatePaths = function translatePaths(files, fileAliases) { if (!_.isArray(files)) { files = [files]; } - return _.flatten(files).map(function interpolateFileAliases (f) { + return _.flatten(files).map(function interpolateFileAliases(f) { return interpolate(f, fileAliases); }); }; -const isVerbose = function isVerbose () { +const isVerbose = function isVerbose() { return process.env.VERBOSE === '1'; }; -const getTestReporter = function getTestReporter (opts) { +const getTestReporter = function getTestReporter(opts) { const isForceLogMode = parseInt(process.env._FORCE_LOGS, 10) === 1; - return isForceLogMode ? 'spec' : (process.env.REPORTER ? process.env.REPORTER : opts.testReporter); + return isForceLogMode ? 'spec' : process.env.REPORTER ? process.env.REPORTER : opts.testReporter; }; -const pExec = function pExec (cmd, args = [], opts = {}) { - return new B(function executeCmd (resolve, reject) { - exec(`${quote([cmd])} ${quote(args)}`, opts, function cb (err, stdout, stderr) { // eslint-disable-line promise/prefer-await-to-callbacks +const pExec = function pExec(cmd, args = [], opts = {}) { + return new B(function executeCmd(resolve, reject) { + exec(`${quote([cmd])} ${quote(args)}`, opts, function cb(err, stdout, stderr) { + // eslint-disable-line promise/prefer-await-to-callbacks if (err) { err.stdout = stdout; err.stderr = stderr; diff --git a/packages/gulp-plugins/lib/yaml-lint.js b/packages/gulp-plugins/lib/yaml-lint.js index e815af8ea..315beb5f9 100644 --- a/packages/gulp-plugins/lib/yaml-lint.js +++ b/packages/gulp-plugins/lib/yaml-lint.js @@ -1,17 +1,16 @@ /* eslint-disable promise/prefer-await-to-callbacks */ -const { Transform } = require('stream'); +const {Transform} = require('stream'); const log = require('fancy-log'); const yaml = require('js-yaml'); -const { EOL } = require('os'); +const {EOL} = require('os'); const PluginError = require('plugin-error'); const red = require('ansi-red'); - -function gulpYamlLint () { +function gulpYamlLint() { let errCount = 0; return new Transform({ objectMode: true, - transform (file, enc, cb) { + transform(file, enc, cb) { try { yaml.load(file.contents.toString(enc)); } catch (err) { @@ -23,14 +22,16 @@ function gulpYamlLint () { } cb(); }, - flush (done) { + flush(done) { if (errCount > 0) { log.error(`YAML errors found. Due to the limitations of YAML linting, the error `); log.error(`is most likely in the line immediately ${red('before')} the line reported.`); - return done(new PluginError('gulp-yaml-lint', { - name: 'YAMLError', - message: `Failed with ${errCount} ${errCount === 1 ? 'error' : 'errors'}`, - })); + return done( + new PluginError('gulp-yaml-lint', { + name: 'YAMLError', + message: `Failed with ${errCount} ${errCount === 1 ? 'error' : 'errors'}`, + }) + ); } done(); }, diff --git a/packages/gulp-plugins/test/gulpfile-js/generate.js b/packages/gulp-plugins/test/gulpfile-js/generate.js index 001dbc2da..3cf36c284 100644 --- a/packages/gulp-plugins/test/gulpfile-js/generate.js +++ b/packages/gulp-plugins/test/gulpfile-js/generate.js @@ -1,45 +1,44 @@ 'use strict'; const gulp = require('gulp'); -const { Transpiler, spawnWatcher, isVerbose } = require('../..'); +const {Transpiler, spawnWatcher, isVerbose} = require('../..'); const _ = require('lodash'); const B = require('bluebird'); -const { exec } = require('../../lib/utils'); +const {exec} = require('../../lib/utils'); const assert = require('assert'); const debug = require('gulp-debug'); const gulpIf = require('gulp-if'); const globby = require('globby'); const rimraf = B.promisify(require('rimraf')); - spawnWatcher.use(gulp); gulp.task('generate-lots-of-files', async function () { await rimraf('test/generated/es7 build/generated'); await exec('mkdir', ['-p', 'test/generated/es7']); await B.all([ - ...( - _.times(24).map(function (i) { - return exec('cp', ['test/fixtures/es7/lib/a.es7.js', `test/generated/es7/a${i + 1}.es7.js`]); - }) - ) + ..._.times(24).map(function (i) { + return exec('cp', ['test/fixtures/es7/lib/a.es7.js', `test/generated/es7/a${i + 1}.es7.js`]); + }), ]); }); gulp.task('transpile-lots-of-es7-files', function () { const transpiler = new Transpiler(); - return gulp.src('test/generated/es7/**/*.js') + return gulp + .src('test/generated/es7/**/*.js') .pipe(gulpIf(isVerbose(), debug())) .pipe(transpiler.stream()) .on('error', spawnWatcher.handleError) .pipe(gulp.dest('build/generated')); }); -gulp.task('transpile-lots-of-files', +gulp.task( + 'transpile-lots-of-files', gulp.series('generate-lots-of-files', 'transpile-lots-of-es7-files') ); -gulp.task('test-transpile-lots-of-es7-files', async function testTranspileLotsOfFiles () { +gulp.task('test-transpile-lots-of-es7-files', async function testTranspileLotsOfFiles() { let files = await globby('test/generated/es7/**/*.js'); const numOfFiles = files.length; assert(numOfFiles > 16); @@ -51,6 +50,7 @@ gulp.task('test-transpile-lots-of-es7-files', async function testTranspileLotsOf assert(files.length === 0); }); -gulp.task('test-transpile-lots-of-files', +gulp.task( + 'test-transpile-lots-of-files', gulp.series('transpile-lots-of-files', 'test-transpile-lots-of-es7-files') ); diff --git a/packages/gulp-plugins/test/gulpfile-js/index.js b/packages/gulp-plugins/test/gulpfile-js/index.js index 455ac2fcc..dff9122a6 100644 --- a/packages/gulp-plugins/test/gulpfile-js/index.js +++ b/packages/gulp-plugins/test/gulpfile-js/index.js @@ -1,17 +1,14 @@ 'use strict'; const gulp = require('gulp'); -const { Transpiler, isVerbose, spawnWatcher } = require('../..'); +const {Transpiler, isVerbose, spawnWatcher} = require('../..'); const vinylPaths = require('vinyl-paths'); const del = require('del'); const debug = require('gulp-debug'); const gulpIf = require('gulp-if'); - gulp.task('clean-fixtures', function () { - return gulp - .src('build-fixtures', {read: false, allowEmpty: true}) - .pipe(vinylPaths(del)); + return gulp.src('build-fixtures', {read: false, allowEmpty: true}).pipe(vinylPaths(del)); }); gulp.task('transpile-es7-fixtures', function () { diff --git a/packages/gulp-plugins/test/gulpfile-js/test-es7.js b/packages/gulp-plugins/test/gulpfile-js/test-es7.js index e46bb521d..f5a45d2bc 100644 --- a/packages/gulp-plugins/test/gulpfile-js/test-es7.js +++ b/packages/gulp-plugins/test/gulpfile-js/test-es7.js @@ -2,13 +2,13 @@ const gulp = require('gulp'); const mocha = require('gulp-mocha'); -const { isVerbose, spawnWatcher } = require('../..'); +const {isVerbose, spawnWatcher} = require('../..'); const debug = require('gulp-debug'); const gulpIf = require('gulp-if'); - gulp.task('test-es7-mocha:run', function () { - return gulp.src('build-fixtures/test/a-specs.js') + return gulp + .src('build-fixtures/test/a-specs.js') .pipe(gulpIf(isVerbose(), debug())) .pipe(mocha()) .on('error', spawnWatcher.handleError); @@ -17,10 +17,14 @@ gulp.task('test-es7-mocha:run', function () { gulp.task('test-es7-mocha', gulp.series('transpile-es7-fixtures', 'test-es7-mocha:run')); gulp.task('test-es7-mocha-throw:run', function () { - return gulp.src('build-fixtures/test/a-throw-specs.js') + return gulp + .src('build-fixtures/test/a-throw-specs.js') .pipe(gulpIf(isVerbose(), debug())) .pipe(mocha()) .on('error', spawnWatcher.handleError); }); -gulp.task('test-es7-mocha-throw', gulp.series('transpile-es7-fixtures', 'test-es7-mocha-throw:run')); +gulp.task( + 'test-es7-mocha-throw', + gulp.series('transpile-es7-fixtures', 'test-es7-mocha-throw:run') +); diff --git a/packages/gulp-plugins/test/transpile-specs.js b/packages/gulp-plugins/test/transpile-specs.js index f68024346..5b28f37c2 100644 --- a/packages/gulp-plugins/test/transpile-specs.js +++ b/packages/gulp-plugins/test/transpile-specs.js @@ -6,7 +6,6 @@ import fs from 'fs'; import _ from 'lodash'; import log from 'fancy-log'; - // XXX: this behavior is unsupported by Node.js (but is supported by Babel). // fix if dropping babel const GULP = require.resolve('gulp/bin/gulp'); @@ -15,7 +14,7 @@ const MOCHA = require.resolve('mocha/bin/mocha'); const readFile = B.promisify(fs.readFile); // we do not care about exec errors -async function exec (...args) { +async function exec(...args) { return await new B(function (resolve) { // eslint-disable-next-line promise/prefer-await-to-callbacks cp.exec(args.join(' '), function (err, stdout, stderr) { @@ -25,7 +24,7 @@ async function exec (...args) { } // some debug -function print (stdout, stderr) { +function print(stdout, stderr) { if (process.env.VERBOSE) { if ((stdout || '').length) { log(`stdout --> '${stdout}'`); @@ -45,7 +44,7 @@ describe('transpile-specs', function () { classFile: 'a', throwFile: 'a-throw.es7.js:7', throwTestFile: 'a-throw-specs.es7.js:8', - } + }, }; for (const [name, files] of _.toPairs(tests)) { @@ -73,7 +72,9 @@ describe('transpile-specs', function () { }); it(`should be able to run transpiled ${name} tests`, async function () { - const [stdout, stderr] = await exec(`${MOCHA} build-fixtures/test/${files.classFile}-specs.js`); + const [stdout, stderr] = await exec( + `${MOCHA} build-fixtures/test/${files.classFile}-specs.js` + ); print(stdout, stderr); stderr.should.equal(''); stdout.should.include('1 passing'); @@ -88,7 +89,9 @@ describe('transpile-specs', function () { }); it(`should use sourcemap when throwing within mocha (${name})`, async function () { - const [stdout, stderr] = await exec(`${MOCHA} build-fixtures/test/${files.classFile}-throw-specs.js`); + const [stdout, stderr] = await exec( + `${MOCHA} build-fixtures/test/${files.classFile}-throw-specs.js` + ); print(stdout, stderr); let output = stdout + stderr; output.should.include('This is really bad!'); diff --git a/packages/images-plugin/lib/compare.js b/packages/images-plugin/lib/compare.js index e571eebb0..d43796467 100644 --- a/packages/images-plugin/lib/compare.js +++ b/packages/images-plugin/lib/compare.js @@ -1,6 +1,6 @@ import _ from 'lodash'; -import { errors } from '@appium/base-driver'; -import { getImagesMatches, getImagesSimilarity, getImageOccurrence } from '@appium/opencv'; +import {errors} from '@appium/base-driver'; +import {getImagesMatches, getImagesSimilarity, getImageOccurrence} from '@appium/opencv'; const MATCH_FEATURES_MODE = 'matchFeatures'; const GET_SIMILARITY_MODE = 'getSimilarity'; @@ -29,7 +29,7 @@ const DEFAULT_MATCH_THRESHOLD = 0.4; * if `mode` value is incorrect or if there was an unexpected issue while * matching the images. */ -async function compareImages (mode, firstImage, secondImage, options = {}) { +async function compareImages(mode, firstImage, secondImage, options = {}) { const img1 = Buffer.from(firstImage, 'base64'); const img2 = Buffer.from(secondImage, 'base64'); let result = null; @@ -53,8 +53,14 @@ async function compareImages (mode, firstImage, secondImage, options = {}) { } break; default: - throw new errors.InvalidArgumentError(`'${mode}' images comparison mode is unknown. ` + - `Only ${JSON.stringify([MATCH_FEATURES_MODE, GET_SIMILARITY_MODE, MATCH_TEMPLATE_MODE])} modes are supported.`); + throw new errors.InvalidArgumentError( + `'${mode}' images comparison mode is unknown. ` + + `Only ${JSON.stringify([ + MATCH_FEATURES_MODE, + GET_SIMILARITY_MODE, + MATCH_TEMPLATE_MODE, + ])} modes are supported.` + ); } return convertVisualizationToBase64(result); } @@ -65,8 +71,8 @@ async function compareImages (mode, firstImage, secondImage, options = {}) { * * @param {OccurrenceResult} element - occurrence result * -**/ -function convertVisualizationToBase64 (element) { + **/ +function convertVisualizationToBase64(element) { if (!_.isEmpty(element.visualization)) { element.visualization = element.visualization.toString('base64'); } @@ -74,8 +80,13 @@ function convertVisualizationToBase64 (element) { return element; } -export { compareImages, DEFAULT_MATCH_THRESHOLD, MATCH_TEMPLATE_MODE, MATCH_FEATURES_MODE, - GET_SIMILARITY_MODE }; +export { + compareImages, + DEFAULT_MATCH_THRESHOLD, + MATCH_TEMPLATE_MODE, + MATCH_FEATURES_MODE, + GET_SIMILARITY_MODE, +}; /** * @typedef {import('@appium/opencv').OccurrenceResult} OccurrenceResult diff --git a/packages/images-plugin/lib/finder.js b/packages/images-plugin/lib/finder.js index b694bfca5..15ae167c3 100644 --- a/packages/images-plugin/lib/finder.js +++ b/packages/images-plugin/lib/finder.js @@ -1,10 +1,13 @@ import _ from 'lodash'; import LRU from 'lru-cache'; -import { imageUtil, util } from '@appium/support'; -import { errors } from '@appium/base-driver'; -import { ImageElement, DEFAULT_TEMPLATE_IMAGE_SCALE, - IMAGE_EL_TAP_STRATEGY_W3C } from './image-element'; -import { MATCH_TEMPLATE_MODE, compareImages, DEFAULT_MATCH_THRESHOLD } from './compare'; +import {imageUtil, util} from '@appium/support'; +import {errors} from '@appium/base-driver'; +import { + ImageElement, + DEFAULT_TEMPLATE_IMAGE_SCALE, + IMAGE_EL_TAP_STRATEGY_W3C, +} from './image-element'; +import {MATCH_TEMPLATE_MODE, compareImages, DEFAULT_MATCH_THRESHOLD} from './compare'; import log from './logger'; const MJSONWP_ELEMENT_KEY = 'ELEMENT'; @@ -70,7 +73,7 @@ const DEFAULT_SETTINGS = { }; export default class ImageElementFinder { - constructor (driver, maxSize = MAX_CACHE_SIZE_BYTES) { + constructor(driver, maxSize = MAX_CACHE_SIZE_BYTES) { this.driver = driver; this.imgElCache = new LRU({ max: MAX_CACHE_ITEMS, @@ -79,11 +82,11 @@ export default class ImageElementFinder { }); } - setDriver (driver) { + setDriver(driver) { this.driver = driver; } - registerImageElement (imgEl) { + registerImageElement(imgEl) { this.imgElCache.set(imgEl.id, imgEl); const protoKey = this.driver.isW3CProtocol() ? W3C_ELEMENT_KEY : MJSONWP_ELEMENT_KEY; return imgEl.asElement(protoKey); @@ -110,11 +113,10 @@ export default class ImageElementFinder { * * @returns {WebElement} - WebDriver element with a special id prefix */ - async findByImage (b64Template, { - shouldCheckStaleness = false, - multiple = false, - ignoreDefaultImageTemplateScale = false, - }) { + async findByImage( + b64Template, + {shouldCheckStaleness = false, multiple = false, ignoreDefaultImageTemplateScale = false} + ) { if (!this.driver) { throw new Error(`Can't find without a driver!`); } @@ -125,7 +127,7 @@ export default class ImageElementFinder { fixImageTemplateSize, fixImageTemplateScale, defaultImageTemplateScale, - getMatchedImageResult: visualize + getMatchedImageResult: visualize, } = settings; log.info(`Finding image element with match threshold ${threshold}`); @@ -139,18 +141,22 @@ export default class ImageElementFinder { // will not work unless we do. But because it requires some potentially // expensive commands, only do this if the user has requested it in settings. if (fixImageTemplateSize) { - b64Template = await this.ensureTemplateSize(b64Template, screenWidth, - screenHeight); + b64Template = await this.ensureTemplateSize(b64Template, screenWidth, screenHeight); } const results = []; const condition = async () => { try { - const {b64Screenshot, scale} = await this.getScreenshotForImageFind(screenWidth, screenHeight); + const {b64Screenshot, scale} = await this.getScreenshotForImageFind( + screenWidth, + screenHeight + ); b64Template = await this.fixImageTemplateScale(b64Template, { - defaultImageTemplateScale, ignoreDefaultImageTemplateScale, - fixImageTemplateScale, ...scale + defaultImageTemplateScale, + ignoreDefaultImageTemplateScale, + fixImageTemplateScale, + ...scale, }); const comparisonOpts = { @@ -162,18 +168,20 @@ export default class ImageElementFinder { comparisonOpts.method = imageMatchMethod; } if (multiple) { - results.push(...(await compareImages(MATCH_TEMPLATE_MODE, - b64Screenshot, - b64Template, - comparisonOpts))); + results.push( + ...(await compareImages( + MATCH_TEMPLATE_MODE, + b64Screenshot, + b64Template, + comparisonOpts + )) + ); } else { - results.push(await compareImages(MATCH_TEMPLATE_MODE, - b64Screenshot, - b64Template, - comparisonOpts)); + results.push( + await compareImages(MATCH_TEMPLATE_MODE, b64Screenshot, b64Template, comparisonOpts) + ); } return true; - } catch (err) { // if compareImages fails, we'll get a specific error, but we should // retry, so trap that and just return false to trigger the next round of @@ -233,18 +241,22 @@ export default class ImageElementFinder { * * @returns {string} base64-encoded image, potentially resized */ - async ensureTemplateSize (b64Template, screenWidth, screenHeight) { + async ensureTemplateSize(b64Template, screenWidth, screenHeight) { let imgObj = await imageUtil.getJimpImage(b64Template); let {width: tplWidth, height: tplHeight} = imgObj.bitmap; - log.info(`Template image is ${tplWidth}x${tplHeight}. Screen size is ${screenWidth}x${screenHeight}`); + log.info( + `Template image is ${tplWidth}x${tplHeight}. Screen size is ${screenWidth}x${screenHeight}` + ); // if the template fits inside the screen dimensions, we're good if (tplWidth <= screenWidth && tplHeight <= screenHeight) { return b64Template; } - log.info(`Scaling template image from ${tplWidth}x${tplHeight} to match ` + - `screen at ${screenWidth}x${screenHeight}`); + log.info( + `Scaling template image from ${tplWidth}x${tplHeight} to match ` + + `screen at ${screenWidth}x${screenHeight}` + ); // otherwise, scale it to fit inside the screen dimensions imgObj = imgObj.scaleToFit(screenWidth, screenHeight); return (await imgObj.getBuffer(imageUtil.MIME_PNG)).toString('base64'); @@ -268,7 +280,7 @@ export default class ImageElementFinder { * * @returns { {b64Screenshot: Screenshot, scale: ScreenshotScale? } } base64-encoded screenshot and ScreenshotScale */ - async getScreenshotForImageFind (screenWidth, screenHeight) { + async getScreenshotForImageFind(screenWidth, screenHeight) { if (!this.driver.getScreenshot) { throw new Error("This driver does not support the required 'getScreenshot' command"); } @@ -285,8 +297,10 @@ export default class ImageElementFinder { } if (screenWidth < 1 || screenHeight < 1) { - log.warn(`The retrieved screen size ${screenWidth}x${screenHeight} does ` + - `not seem to be valid. No changes will be applied to the screenshot`); + log.warn( + `The retrieved screen size ${screenWidth}x${screenHeight} does ` + + `not seem to be valid. No changes will be applied to the screenshot` + ); return {b64Screenshot}; } @@ -298,8 +312,10 @@ export default class ImageElementFinder { let {width: shotWidth, height: shotHeight} = imgObj.bitmap; if (shotWidth < 1 || shotHeight < 1) { - log.warn(`The retrieved screenshot size ${shotWidth}x${shotHeight} does ` + - `not seem to be valid. No changes will be applied to the screenshot`); + log.warn( + `The retrieved screenshot size ${shotWidth}x${shotHeight} does ` + + `not seem to be valid. No changes will be applied to the screenshot` + ); return {b64Screenshot}; } @@ -321,13 +337,17 @@ export default class ImageElementFinder { const screenAR = screenWidth / screenHeight; const shotAR = shotWidth / shotHeight; if (Math.round(screenAR * FLOAT_PRECISION) === Math.round(shotAR * FLOAT_PRECISION)) { - log.info(`Screenshot aspect ratio '${shotAR}' (${shotWidth}x${shotHeight}) matched ` + - `screen aspect ratio '${screenAR}' (${screenWidth}x${screenHeight})`); + log.info( + `Screenshot aspect ratio '${shotAR}' (${shotWidth}x${shotHeight}) matched ` + + `screen aspect ratio '${screenAR}' (${screenWidth}x${screenHeight})` + ); } else { - log.warn(`When trying to find an element, determined that the screen ` + - `aspect ratio and screenshot aspect ratio are different. Screen ` + - `is ${screenWidth}x${screenHeight} whereas screenshot is ` + - `${shotWidth}x${shotHeight}.`); + log.warn( + `When trying to find an element, determined that the screen ` + + `aspect ratio and screenshot aspect ratio are different. Screen ` + + `is ${screenWidth}x${screenHeight} whereas screenshot is ` + + `${shotWidth}x${shotHeight}.` + ); // In the case where the x-scale and y-scale are different, we need to decide // which one to respect, otherwise the screenshot and template will end up @@ -344,9 +364,11 @@ export default class ImageElementFinder { const yScale = (1.0 * shotHeight) / screenHeight; const scaleFactor = xScale >= yScale ? yScale : xScale; - log.warn(`Resizing screenshot to ${shotWidth * scaleFactor}x${shotHeight * scaleFactor} to match ` + - `screen aspect ratio so that image element coordinates have a ` + - `greater chance of being correct.`); + log.warn( + `Resizing screenshot to ${shotWidth * scaleFactor}x${shotHeight * scaleFactor} to match ` + + `screen aspect ratio so that image element coordinates have a ` + + `greater chance of being correct.` + ); imgObj = imgObj.resize(shotWidth * scaleFactor, shotHeight * scaleFactor); scale.xScale *= scaleFactor; @@ -361,8 +383,10 @@ export default class ImageElementFinder { // screenshot size like `@driver.window_rect #=>x=0, y=0, width=1080, height=1794` and // `"deviceScreenSize"=>"1080x1920"` if (screenWidth !== shotWidth && screenHeight !== shotHeight) { - log.info(`Scaling screenshot from ${shotWidth}x${shotHeight} to match ` + - `screen at ${screenWidth}x${screenHeight}`); + log.info( + `Scaling screenshot from ${shotWidth}x${shotHeight} to match ` + + `screen at ${screenWidth}x${screenHeight}` + ); imgObj = imgObj.resize(screenWidth, screenHeight); scale.xScale *= (1.0 * screenWidth) / shotWidth; @@ -395,7 +419,7 @@ export default class ImageElementFinder { * * @returns {string} base64-encoded scaled template screenshot */ - async fixImageTemplateScale (b64Template, opts = {}) { + async fixImageTemplateScale(b64Template, opts = {}) { if (!opts) { return b64Template; } @@ -405,7 +429,7 @@ export default class ImageElementFinder { defaultImageTemplateScale = DEFAULT_TEMPLATE_IMAGE_SCALE, ignoreDefaultImageTemplateScale = false, xScale = DEFAULT_FIX_IMAGE_TEMPLATE_SCALE, - yScale = DEFAULT_FIX_IMAGE_TEMPLATE_SCALE + yScale = DEFAULT_FIX_IMAGE_TEMPLATE_SCALE, } = opts; if (ignoreDefaultImageTemplateScale) { @@ -431,8 +455,13 @@ export default class ImageElementFinder { } // Return if the scale is default, 1, value - if (Math.round(xScale * FLOAT_PRECISION) === Math.round(DEFAULT_FIX_IMAGE_TEMPLATE_SCALE * FLOAT_PRECISION) - && Math.round(yScale * FLOAT_PRECISION === Math.round(DEFAULT_FIX_IMAGE_TEMPLATE_SCALE * FLOAT_PRECISION))) { + if ( + Math.round(xScale * FLOAT_PRECISION) === + Math.round(DEFAULT_FIX_IMAGE_TEMPLATE_SCALE * FLOAT_PRECISION) && + Math.round( + yScale * FLOAT_PRECISION === Math.round(DEFAULT_FIX_IMAGE_TEMPLATE_SCALE * FLOAT_PRECISION) + ) + ) { return b64Template; } @@ -441,12 +470,14 @@ export default class ImageElementFinder { const scaledWidth = baseTempWidth * xScale; const scaledHeight = baseTempHeigh * yScale; - log.info(`Scaling template image from ${baseTempWidth}x${baseTempHeigh}` + - ` to ${scaledWidth}x${scaledHeight}`); + log.info( + `Scaling template image from ${baseTempWidth}x${baseTempHeigh}` + + ` to ${scaledWidth}x${scaledHeight}` + ); log.info(`The ratio is ${xScale} and ${yScale}`); imgTempObj = await imgTempObj.resize(scaledWidth, scaledHeight); return (await imgTempObj.getBuffer(imageUtil.MIME_PNG)).toString('base64'); } } -export { W3C_ELEMENT_KEY, MJSONWP_ELEMENT_KEY, DEFAULT_SETTINGS, DEFAULT_FIX_IMAGE_TEMPLATE_SCALE }; +export {W3C_ELEMENT_KEY, MJSONWP_ELEMENT_KEY, DEFAULT_SETTINGS, DEFAULT_FIX_IMAGE_TEMPLATE_SCALE}; diff --git a/packages/images-plugin/lib/image-element.js b/packages/images-plugin/lib/image-element.js index 07c63ccbe..32197aa67 100644 --- a/packages/images-plugin/lib/image-element.js +++ b/packages/images-plugin/lib/image-element.js @@ -1,17 +1,14 @@ import _ from 'lodash'; -import { errors } from '@appium/base-driver'; +import {errors} from '@appium/base-driver'; import log from './logger'; -import { util } from '@appium/support'; -import { DEFAULT_SETTINGS } from './finder'; +import {util} from '@appium/support'; +import {DEFAULT_SETTINGS} from './finder'; const IMAGE_ELEMENT_PREFIX = 'appium-image-element-'; const TAP_DURATION_MS = 125; const IMAGE_EL_TAP_STRATEGY_W3C = 'w3cActions'; const IMAGE_EL_TAP_STRATEGY_MJSONWP = 'touchActions'; -const IMAGE_TAP_STRATEGIES = [ - IMAGE_EL_TAP_STRATEGY_MJSONWP, - IMAGE_EL_TAP_STRATEGY_W3C -]; +const IMAGE_TAP_STRATEGIES = [IMAGE_EL_TAP_STRATEGY_MJSONWP, IMAGE_EL_TAP_STRATEGY_W3C]; const DEFAULT_TEMPLATE_IMAGE_SCALE = 1.0; /** @@ -31,7 +28,6 @@ const DEFAULT_TEMPLATE_IMAGE_SCALE = 1.0; * and methods that can be used on that set of coordinates via the driver */ export default class ImageElement { - /** * @param {string} b64Template - the base64-encoded image which was used to * find this ImageElement @@ -42,7 +38,7 @@ export default class ImageElement { * Defaults to null. * @param {import('./finder').default?} finder - the finder we can use to re-check stale elements */ - constructor (b64Template, rect, score, b64Result = null, finder = null) { + constructor(b64Template, rect, score, b64Result = null, finder = null) { this.template = b64Template; this.rect = rect; this.id = `${IMAGE_ELEMENT_PREFIX}${util.uuidV4()}`; @@ -54,21 +50,21 @@ export default class ImageElement { /** * @returns {Dimension} - dimension of element */ - get size () { + get size() { return {width: this.rect.width, height: this.rect.height}; } /** * @returns {Position} - coordinates of top-left corner of element */ - get location () { + get location() { return {x: this.rect.x, y: this.rect.y}; } /** * @returns {Position} - coordinates of center of element */ - get center () { + get center() { return { x: this.rect.x + this.rect.width / 2, y: this.rect.y + this.rect.height / 2, @@ -78,7 +74,7 @@ export default class ImageElement { /** * @returns {?string} - the base64-encoded image which has matched marks */ - get matchedImage () { + get matchedImage() { return this.b64MatchedImage; } @@ -88,7 +84,7 @@ export default class ImageElement { * * @returns {WebElement} - this image element as a WebElement */ - asElement (protocolKey) { + asElement(protocolKey) { return {[protocolKey]: this.id}; } @@ -98,11 +94,13 @@ export default class ImageElement { * @returns {boolean} - whether the other element and this one have the same * properties */ - equals (other) { - return this.rect.x === other.rect.x && - this.rect.y === other.rect.y && - this.rect.width === other.rect.width && - this.rect.height === other.rect.height; + equals(other) { + return ( + this.rect.x === other.rect.x && + this.rect.y === other.rect.y && + this.rect.width === other.rect.width && + this.rect.height === other.rect.height + ); } /** @@ -111,7 +109,7 @@ export default class ImageElement { * * @param {BaseDriver} driver - driver for calling actions with */ - async click (driver) { + async click(driver) { // before we click we need to make sure the element is actually still there // where we expect it to be let newImgEl; @@ -124,9 +122,11 @@ export default class ImageElement { // validate tap strategy if (!IMAGE_TAP_STRATEGIES.includes(imageElementTapStrategy)) { - throw new Error(`Incorrect imageElementTapStrategy setting ` + - `'${imageElementTapStrategy}'. Must be one of ` + - JSON.stringify(IMAGE_TAP_STRATEGIES)); + throw new Error( + `Incorrect imageElementTapStrategy setting ` + + `'${imageElementTapStrategy}'. Must be one of ` + + JSON.stringify(IMAGE_TAP_STRATEGIES) + ); } if (checkForImageElementStaleness || updatePos) { @@ -136,25 +136,29 @@ export default class ImageElement { shouldCheckStaleness: true, // Set ignoreDefaultImageTemplateScale because this.template is device screenshot based image // managed inside Appium after finidng image by template which managed by a user - ignoreDefaultImageTemplateScale: true + ignoreDefaultImageTemplateScale: true, }); } catch (err) { throw new errors.StaleElementReferenceError(); } if (!this.equals(newImgEl)) { - log.warn(`When trying to click on an image element, the image changed ` + - `position from where it was originally found. It is now at ` + - `${JSON.stringify(newImgEl.rect)} and was originally at ` + - `${JSON.stringify(this.rect)}.`); + log.warn( + `When trying to click on an image element, the image changed ` + + `position from where it was originally found. It is now at ` + + `${JSON.stringify(newImgEl.rect)} and was originally at ` + + `${JSON.stringify(this.rect)}.` + ); if (updatePos) { log.warn('Click will proceed at new coordinates'); this.rect = _.clone(newImgEl.rect); } else { - log.warn('Click will take place at original coordinates. If you ' + - 'would like Appium to automatically click the new ' + - "coordinates, set the 'autoUpdateImageElementPosition' " + - 'setting to true'); + log.warn( + 'Click will take place at original coordinates. If you ' + + 'would like Appium to automatically click the new ' + + "coordinates, set the 'autoUpdateImageElementPosition' " + + 'setting to true' + ); } } } @@ -174,7 +178,7 @@ export default class ImageElement { {type: 'pointerDown', button: 0}, {type: 'pause', duration: TAP_DURATION_MS}, {type: 'pointerUp', button: 0}, - ] + ], }; // check if the driver has the appropriate performActions method @@ -183,8 +187,7 @@ export default class ImageElement { } // if not, warn and fall back to the other method - log.warn('Driver does not seem to implement W3C actions, falling back ' + - 'to TouchActions'); + log.warn('Driver does not seem to implement W3C actions, falling back ' + 'to TouchActions'); } // if the w3c strategy was not requested, do the only other option (mjsonwp @@ -192,16 +195,18 @@ export default class ImageElement { log.info('Will tap using MJSONWP TouchActions'); const action = { action: 'tap', - options: {x, y} + options: {x, y}, }; if (driver.performTouch) { return await driver.performTouch([action]); } - throw new Error("Driver did not implement the 'performTouch' command. " + - 'For drivers to support finding image elements, they ' + - "should support 'performTouch' and 'performActions'"); + throw new Error( + "Driver did not implement the 'performTouch' command. " + + 'For drivers to support finding image elements, they ' + + "should support 'performTouch' and 'performActions'" + ); } /** @@ -214,7 +219,7 @@ export default class ImageElement { * * @returns {object} - the result of running a command */ - static async execute (driver, imgEl, cmd, ...args) { + static async execute(driver, imgEl, cmd, ...args) { switch (cmd) { case 'click': return await imgEl.click(driver); @@ -239,14 +244,18 @@ export default class ImageElement { default: throw new errors.NotYetImplementedError(); } - default: throw new errors.NotYetImplementedError(); + default: + throw new errors.NotYetImplementedError(); } } } export { - ImageElement, IMAGE_EL_TAP_STRATEGY_MJSONWP, IMAGE_EL_TAP_STRATEGY_W3C, - DEFAULT_TEMPLATE_IMAGE_SCALE, IMAGE_ELEMENT_PREFIX + ImageElement, + IMAGE_EL_TAP_STRATEGY_MJSONWP, + IMAGE_EL_TAP_STRATEGY_W3C, + DEFAULT_TEMPLATE_IMAGE_SCALE, + IMAGE_ELEMENT_PREFIX, }; /** diff --git a/packages/images-plugin/lib/logger.js b/packages/images-plugin/lib/logger.js index 27f620d63..4c652c928 100644 --- a/packages/images-plugin/lib/logger.js +++ b/packages/images-plugin/lib/logger.js @@ -1,3 +1,3 @@ -import { logger } from '@appium/support'; +import {logger} from '@appium/support'; const log = logger.getLogger('ImageElementPlugin'); export default log; diff --git a/packages/images-plugin/lib/plugin.js b/packages/images-plugin/lib/plugin.js index 9a3e967e9..24cb1a5d3 100644 --- a/packages/images-plugin/lib/plugin.js +++ b/packages/images-plugin/lib/plugin.js @@ -1,15 +1,15 @@ /* eslint-disable no-case-declarations */ import _ from 'lodash'; -import { errors } from '@appium/base-driver'; +import {errors} from '@appium/base-driver'; import BasePlugin from '@appium/base-plugin'; -import { compareImages } from './compare'; +import {compareImages} from './compare'; import ImageElementFinder from './finder'; -import { ImageElement, IMAGE_ELEMENT_PREFIX } from './image-element'; +import {ImageElement, IMAGE_ELEMENT_PREFIX} from './image-element'; const IMAGE_STRATEGY = '-image'; -function getImgElFromArgs (args) { +function getImgElFromArgs(args) { for (let arg of args) { if (_.isString(arg) && arg.startsWith(IMAGE_ELEMENT_PREFIX)) { return arg; @@ -18,8 +18,7 @@ function getImgElFromArgs (args) { } export default class ImageElementPlugin extends BasePlugin { - - constructor (pluginName) { + constructor(pluginName) { super(pluginName); this.finder = new ImageElementFinder(); } @@ -31,26 +30,26 @@ export default class ImageElementPlugin extends BasePlugin { command: 'compareImages', payloadParams: { required: ['mode', 'firstImage', 'secondImage'], - optional: ['options'] + optional: ['options'], }, neverProxy: true, - } + }, }, }; - async compareImages (next, driver, ...args) { + async compareImages(next, driver, ...args) { return await compareImages(...args); } - async findElement (next, driver, ...args) { + async findElement(next, driver, ...args) { return await this._find(false, next, driver, ...args); } - async findElements (next, driver, ...args) { + async findElements(next, driver, ...args) { return await this._find(true, next, driver, ...args); } - async _find (multiple, next, driver, ...args) { + async _find(multiple, next, driver, ...args) { const [strategy, selector] = args; // if we're not actually finding by image, just do the normal thing @@ -62,7 +61,7 @@ export default class ImageElementPlugin extends BasePlugin { return await this.finder.findByImage(selector, {multiple}); } - async handle (next, driver, cmdName, ...args) { + async handle(next, driver, cmdName, ...args) { // if we have a command that involves an image element id, attempt to find the image element // and execute the command on it const imgElId = getImgElFromArgs(args); @@ -79,4 +78,4 @@ export default class ImageElementPlugin extends BasePlugin { } } -export { ImageElementPlugin, getImgElFromArgs, IMAGE_STRATEGY }; +export {ImageElementPlugin, getImgElFromArgs, IMAGE_STRATEGY}; diff --git a/packages/images-plugin/test/e2e/plugin.e2e.spec.js b/packages/images-plugin/test/e2e/plugin.e2e.spec.js index 83e936b50..81ad0ca89 100644 --- a/packages/images-plugin/test/e2e/plugin.e2e.spec.js +++ b/packages/images-plugin/test/e2e/plugin.e2e.spec.js @@ -1,31 +1,39 @@ import path from 'path'; -import { remote as wdio } from 'webdriverio'; -import { MATCH_FEATURES_MODE, GET_SIMILARITY_MODE } from '../../lib/compare'; -import { TEST_IMG_1_B64, TEST_IMG_2_B64, APPSTORE_IMG_PATH } from '../fixtures'; -import { e2eSetup } from '@appium/base-plugin/build/test/helpers'; +import {remote as wdio} from 'webdriverio'; +import {MATCH_FEATURES_MODE, GET_SIMILARITY_MODE} from '../../lib/compare'; +import {TEST_IMG_1_B64, TEST_IMG_2_B64, APPSTORE_IMG_PATH} from '../fixtures'; +import {e2eSetup} from '@appium/base-plugin/build/test/helpers'; const THIS_PLUGIN_DIR = path.join(__dirname, '..', '..'); const APPIUM_HOME = path.join(THIS_PLUGIN_DIR, 'local_appium_home'); const FAKE_DRIVER_DIR = path.join(THIS_PLUGIN_DIR, '..', 'fake-driver'); const TEST_HOST = 'localhost'; const TEST_PORT = 4723; -const TEST_FAKE_APP = path.join(APPIUM_HOME, 'node_modules', '@appium', 'fake-driver', 'test', 'fixtures', - 'app.xml'); +const TEST_FAKE_APP = path.join( + APPIUM_HOME, + 'node_modules', + '@appium', + 'fake-driver', + 'test', + 'fixtures', + 'app.xml' +); const TEST_CAPS = { platformName: 'Fake', 'appium:automationName': 'Fake', 'appium:deviceName': 'Fake', - 'appium:app': TEST_FAKE_APP + 'appium:app': TEST_FAKE_APP, }; const WDIO_OPTS = { hostname: TEST_HOST, port: TEST_PORT, connectionRetryCount: 0, - capabilities: TEST_CAPS + capabilities: TEST_CAPS, }; describe('ImageElementPlugin', function () { - let server, driver = null; + let server, + driver = null; // this hook is intended to be run before the hooks created by `e2eSetup` after(async function () { @@ -35,16 +43,35 @@ describe('ImageElementPlugin', function () { }); e2eSetup({ - before, after, server, port: TEST_PORT, host: TEST_HOST, appiumHome: APPIUM_HOME, - driverName: 'fake', driverSource: 'local', driverSpec: FAKE_DRIVER_DIR, - pluginName: 'images', pluginSource: 'local', pluginSpec: THIS_PLUGIN_DIR, + before, + after, + server, + port: TEST_PORT, + host: TEST_HOST, + appiumHome: APPIUM_HOME, + driverName: 'fake', + driverSource: 'local', + driverSpec: FAKE_DRIVER_DIR, + pluginName: 'images', + pluginSource: 'local', + pluginSpec: THIS_PLUGIN_DIR, }); it('should add the compareImages route', async function () { driver = await wdio(WDIO_OPTS); - let comparison = await driver.compareImages(MATCH_FEATURES_MODE, TEST_IMG_1_B64, TEST_IMG_2_B64, {}); + let comparison = await driver.compareImages( + MATCH_FEATURES_MODE, + TEST_IMG_1_B64, + TEST_IMG_2_B64, + {} + ); comparison.count.should.eql(0); - comparison = await driver.compareImages(GET_SIMILARITY_MODE, TEST_IMG_1_B64, TEST_IMG_2_B64, {}); + comparison = await driver.compareImages( + GET_SIMILARITY_MODE, + TEST_IMG_1_B64, + TEST_IMG_2_B64, + {} + ); comparison.score.should.be.above(0.2); }); diff --git a/packages/images-plugin/test/unit/finder.spec.js b/packages/images-plugin/test/unit/finder.spec.js index de9049a5a..040eb2a29 100644 --- a/packages/images-plugin/test/unit/finder.spec.js +++ b/packages/images-plugin/test/unit/finder.spec.js @@ -1,23 +1,23 @@ import _ from 'lodash'; -import { imageUtil } from '@appium/support'; +import {imageUtil} from '@appium/support'; import BaseDriver from '@appium/base-driver'; -import { ImageElementPlugin, IMAGE_STRATEGY } from '../../lib/plugin'; +import {ImageElementPlugin, IMAGE_STRATEGY} from '../../lib/plugin'; import ImageElementFinder from '../../lib/finder'; import ImageElement from '../../lib/image-element'; import sinon from 'sinon'; -import { TINY_PNG, TINY_PNG_DIMS } from '../fixtures'; +import {TINY_PNG, TINY_PNG_DIMS} from '../fixtures'; const compareModule = require('../../lib/compare'); const plugin = new ImageElementPlugin(); class PluginDriver extends BaseDriver { - async getWindowSize () {} - async getScreenshot () {} - findElement (strategy, selector) { + async getWindowSize() {} + async getScreenshot() {} + findElement(strategy, selector) { return plugin.findElement(_.noop, this, strategy, selector); } - findElements (strategy, selector) { + findElements(strategy, selector) { return plugin.findElements(_.noop, this, strategy, selector); } } @@ -43,9 +43,11 @@ describe('finding elements by image', function () { }); it('should not be able to find image element from any other element', async function () { const d = new PluginDriver(); - await d.findElementFromElement(IMAGE_STRATEGY, 'foo', 'elId') + await d + .findElementFromElement(IMAGE_STRATEGY, 'foo', 'elId') .should.be.rejectedWith(/Locator Strategy.+is not supported/); - await d.findElementsFromElement(IMAGE_STRATEGY, 'foo', 'elId') + await d + .findElementsFromElement(IMAGE_STRATEGY, 'foo', 'elId') .should.be.rejectedWith(/Locator Strategy.+is not supported/); }); }); @@ -60,13 +62,13 @@ describe('finding elements by image', function () { let d = new PluginDriver(); let f = new ImageElementFinder(d); - function basicStub (driver, finder) { + function basicStub(driver, finder) { const sizeStub = sandbox.stub(driver, 'getWindowSize').returns(size); const screenStub = sandbox.stub(finder, 'getScreenshotForImageFind').returns(screenshot); return {sizeStub, screenStub}; } - function basicImgElVerify (imgElProto, finder) { + function basicImgElVerify(imgElProto, finder) { const imgElId = imgElProto.ELEMENT; finder.imgElCache.has(imgElId).should.be.true; const imgEl = finder.imgElCache.get(imgElId); @@ -96,7 +98,8 @@ describe('finding elements by image', function () { }); it('should fail if driver does not support getWindowSize', async function () { d.getWindowSize = null; - await f.findByImage(template, {multiple: false}) + await f + .findByImage(template, {multiple: false}) .should.eventually.be.rejectedWith(/driver does not support/); }); it('should fix template size if requested', async function () { @@ -127,7 +130,8 @@ describe('finding elements by image', function () { it('should throw an error if template match fails', async function () { compareStub.throws(new Error('Cannot find any occurrences')); - await f.findByImage(template, {multiple: false}) + await f + .findByImage(template, {multiple: false}) .should.eventually.be.rejectedWith(/element could not be located/); }); it('should return empty array for multiple elements if template match fails', async function () { @@ -144,7 +148,10 @@ describe('finding elements by image', function () { compareStub.callCount.should.eql(2); }); it('should not add element to cache and return it directly when checking staleness', async function () { - const imgEl = await f.findByImage(template, {multiple: false, shouldCheckStaleness: true}); + const imgEl = await f.findByImage(template, { + multiple: false, + shouldCheckStaleness: true, + }); (imgEl instanceof ImageElement).should.be.true; f.imgElCache.has(imgEl.id).should.be.false; imgEl.rect.should.eql(rect); @@ -157,73 +164,100 @@ describe('finding elements by image', function () { const basicTemplate = 'iVBORbaz'; it('should not fix template size scale if no scale value', async function () { - await f.fixImageTemplateScale(basicTemplate, {fixImageTemplateScale: true}) + await f + .fixImageTemplateScale(basicTemplate, {fixImageTemplateScale: true}) .should.eventually.eql(basicTemplate); }); it('should not fix template size scale if it is null', async function () { - await f.fixImageTemplateScale(basicTemplate, null) - .should.eventually.eql(basicTemplate); + await f.fixImageTemplateScale(basicTemplate, null).should.eventually.eql(basicTemplate); }); it('should not fix template size scale if it is not number', async function () { - await f.fixImageTemplateScale(basicTemplate, 'wrong-scale') + await f + .fixImageTemplateScale(basicTemplate, 'wrong-scale') .should.eventually.eql(basicTemplate); }); it('should fix template size scale', async function () { - const actual = 'iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAWElEQVR4AU3BQRWAQAhAwa/PGBsEgrC16AFBKEIPXW7OXO+Rmey9iQjMjHFzrLUwM7qbqmLcHKpKRFBVuDvj4agq3B1VRUQYT2bS3QwRQVUZF/CaGRHB3wc1vSZbHO5+BgAAAABJRU5ErkJggg=='; - await f.fixImageTemplateScale(TINY_PNG, { - fixImageTemplateScale: true, xScale: 1.5, yScale: 1.5 - }).should.eventually.eql(actual); + const actual = + 'iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAWElEQVR4AU3BQRWAQAhAwa/PGBsEgrC16AFBKEIPXW7OXO+Rmey9iQjMjHFzrLUwM7qbqmLcHKpKRFBVuDvj4agq3B1VRUQYT2bS3QwRQVUZF/CaGRHB3wc1vSZbHO5+BgAAAABJRU5ErkJggg=='; + await f + .fixImageTemplateScale(TINY_PNG, { + fixImageTemplateScale: true, + xScale: 1.5, + yScale: 1.5, + }) + .should.eventually.eql(actual); }); it('should not fix template size scale because of fixImageTemplateScale being false', async function () { - await f.fixImageTemplateScale(TINY_PNG, { - fixImageTemplateScale: false, xScale: 1.5, yScale: 1.5 - }).should.eventually.eql(TINY_PNG); + await f + .fixImageTemplateScale(TINY_PNG, { + fixImageTemplateScale: false, + xScale: 1.5, + yScale: 1.5, + }) + .should.eventually.eql(TINY_PNG); }); it('should fix template size scale with default scale', async function () { - const actual = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABwUlEQVR4AaXBPUsrQQCG0SeX+cBdkTjwTpG1NPgLpjY/fW1stt4UYmm2cJqwMCsaw70uJJ3CBc9Z/P3Cl+12S9u2tG1L27bEGLm/v2ez2bDZbJDEd/7wS4YT7z3X19fc3Nxwd3dHXdd47xnHkefnZ8ZxpKoq6rqmqiqMMcwMJ1VV0TQN0zThnOPj44O6rsk503UdkmiahqZpWK1WGGOYGU7quqZpGqy1SCLnTM6Z19dXcs5IYpomrLVI4uLigpnhpKoqVqsVkjgcDjw9PdF1HTlnuq5DEs45JHE4HDgznByPR97e3pimiVIK4zhyPB7x3hNCIITA5eUl3nsWiwVnhpNSCsMwsNvtGIaB/X5PKQVJpJSQxHq9RhLOOc4MJ9M0sdvt2G639H3PTBIxRiQhCUnEGLHWcmY4KaUwDAN93/P4+MhyuSSlhCRSSkjCOYe1FmstZ6bve2YvLy/s93tmy+USSUhCEpIIIfAd8/DwwOz9/Z1SCpJIKSGJ9XqNJJxz/MS0bcvs6uoKScQYkYQkJBFjxFrLT0zbtsxub29JKSGJlBKScM5hrcVay09MzplZjJHPz0+894QQCCHwP/7wS/8A4e6nAg+R8LwAAAAASUVORK5CYII='; - await f.fixImageTemplateScale(TINY_PNG, { - defaultImageTemplateScale: 4.0 - }).should.eventually.eql(actual); + const actual = + 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABwUlEQVR4AaXBPUsrQQCG0SeX+cBdkTjwTpG1NPgLpjY/fW1stt4UYmm2cJqwMCsaw70uJJ3CBc9Z/P3Cl+12S9u2tG1L27bEGLm/v2ez2bDZbJDEd/7wS4YT7z3X19fc3Nxwd3dHXdd47xnHkefnZ8ZxpKoq6rqmqiqMMcwMJ1VV0TQN0zThnOPj44O6rsk503UdkmiahqZpWK1WGGOYGU7quqZpGqy1SCLnTM6Z19dXcs5IYpomrLVI4uLigpnhpKoqVqsVkjgcDjw9PdF1HTlnuq5DEs45JHE4HDgznByPR97e3pimiVIK4zhyPB7x3hNCIITA5eUl3nsWiwVnhpNSCsMwsNvtGIaB/X5PKQVJpJSQxHq9RhLOOc4MJ9M0sdvt2G639H3PTBIxRiQhCUnEGLHWcmY4KaUwDAN93/P4+MhyuSSlhCRSSkjCOYe1FmstZ6bve2YvLy/s93tmy+USSUhCEpIIIfAd8/DwwOz9/Z1SCpJIKSGJ9XqNJJxz/MS0bcvs6uoKScQYkYQkJBFjxFrLT0zbtsxub29JKSGJlBKScM5hrcVay09MzplZjJHPz0+894QQCCHwP/7wS/8A4e6nAg+R8LwAAAAASUVORK5CYII='; + await f + .fixImageTemplateScale(TINY_PNG, { + defaultImageTemplateScale: 4.0, + }) + .should.eventually.eql(actual); }); it('should fix template size scale with default scale and image scale', async function () { - const actual = 'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACaUlEQVR4AbXBMWvrWBSF0c9BsFPtW91UR1U6+///FKlKKt8qqnyqnMozggkI8xgMj6x1uv+L/6zryrIsrOvKsiys68qyLFwuF87nM5fLhfP5zOVy4Xw+84wXftkLv2ziQBK26b0TEVQVu4jANrvM5Hq9spOEJCQhCUlI4mjiQBK26b1TVewkYRvb7DKTMQaZiW1s01rDNraRxNHEgSRaa1QVO0m01jjKTDKTXe+d3jtVxU4SjyYOJGGbnSRs03snM8lMMpPb7UZmkplEBFXFThK2eTRxIAnbSMI2VcX39zdjDMYYZCaZyRiDMQZVxU4StqkqHk0cSEISf5KZ7DKTMQbLsrCTRGuN3jtVxaOJg6qiqqgqqoqqoqoYY5CZ7GwTEdzvd97f34kIeu/YRhKPJg6qiswkM7ndbmQmmUlmkpnsbBMR2CYimOeZ3ju2kcSjiYOqIjP5+vpi2za2bWPbNo5aa7TW2PXe6b3Te6e1hiQeTRxUFbfbjW3bGGNwvV4ZY2Ab27TWsI1tbGMb27TWsI0kHk0cVBWZybZtXK9XPj8/+fj4YJ5nIoLWGraJCOZ5RhKSkIQkJPFo4qCqyEy2bWOMwefnJ+u6cjqdsM3ONvM8cz6feca0ris/rtcrmcnONhHB/X7n/f2diKD3jm0k8axpWRZ+ZCaZyc42EYFtIoJ5num9YxtJPGta15U/sY1tdm9vb/Te6b1jG0k8a1qWhR+2sU1rjdYatrGNbWxjm9YaknjWtK4rPyKCiKC1hm0igojg9fUVSUhCEpJ41rQsC0e22dkmIrhcLvyNF/7H6XTib73wy174Zf8AJEsePtlPj10AAAAASUVORK5CYII='; - await f.fixImageTemplateScale(TINY_PNG, { - defaultImageTemplateScale: 4.0, - fixImageTemplateScale: true, - xScale: 1.5, yScale: 1.5 - }).should.eventually.eql(actual); + const actual = + 'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACaUlEQVR4AbXBMWvrWBSF0c9BsFPtW91UR1U6+///FKlKKt8qqnyqnMozggkI8xgMj6x1uv+L/6zryrIsrOvKsiys68qyLFwuF87nM5fLhfP5zOVy4Xw+84wXftkLv2ziQBK26b0TEVQVu4jANrvM5Hq9spOEJCQhCUlI4mjiQBK26b1TVewkYRvb7DKTMQaZiW1s01rDNraRxNHEgSRaa1QVO0m01jjKTDKTXe+d3jtVxU4SjyYOJGGbnSRs03snM8lMMpPb7UZmkplEBFXFThK2eTRxIAnbSMI2VcX39zdjDMYYZCaZyRiDMQZVxU4StqkqHk0cSEISf5KZ7DKTMQbLsrCTRGuN3jtVxaOJg6qiqqgqqoqqoqoYY5CZ7GwTEdzvd97f34kIeu/YRhKPJg6qiswkM7ndbmQmmUlmkpnsbBMR2CYimOeZ3ju2kcSjiYOqIjP5+vpi2za2bWPbNo5aa7TW2PXe6b3Te6e1hiQeTRxUFbfbjW3bGGNwvV4ZY2Ab27TWsI1tbGMb27TWsI0kHk0cVBWZybZtXK9XPj8/+fj4YJ5nIoLWGraJCOZ5RhKSkIQkJPFo4qCqyEy2bWOMwefnJ+u6cjqdsM3ONvM8cz6feca0ris/rtcrmcnONhHB/X7n/f2diKD3jm0k8axpWRZ+ZCaZyc42EYFtIoJ5num9YxtJPGta15U/sY1tdm9vb/Te6b1jG0k8a1qWhR+2sU1rjdYatrGNbWxjm9YaknjWtK4rPyKCiKC1hm0igojg9fUVSUhCEpJ41rQsC0e22dkmIrhcLvyNF/7H6XTib73wy174Zf8AJEsePtlPj10AAAAASUVORK5CYII='; + await f + .fixImageTemplateScale(TINY_PNG, { + defaultImageTemplateScale: 4.0, + fixImageTemplateScale: true, + xScale: 1.5, + yScale: 1.5, + }) + .should.eventually.eql(actual); }); it('should not fix template size scale with default scale and image scale', async function () { - const actual = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABwUlEQVR4AaXBPUsrQQCG0SeX+cBdkTjwTpG1NPgLpjY/fW1stt4UYmm2cJqwMCsaw70uJJ3CBc9Z/P3Cl+12S9u2tG1L27bEGLm/v2ez2bDZbJDEd/7wS4YT7z3X19fc3Nxwd3dHXdd47xnHkefnZ8ZxpKoq6rqmqiqMMcwMJ1VV0TQN0zThnOPj44O6rsk503UdkmiahqZpWK1WGGOYGU7quqZpGqy1SCLnTM6Z19dXcs5IYpomrLVI4uLigpnhpKoqVqsVkjgcDjw9PdF1HTlnuq5DEs45JHE4HDgznByPR97e3pimiVIK4zhyPB7x3hNCIITA5eUl3nsWiwVnhpNSCsMwsNvtGIaB/X5PKQVJpJSQxHq9RhLOOc4MJ9M0sdvt2G639H3PTBIxRiQhCUnEGLHWcmY4KaUwDAN93/P4+MhyuSSlhCRSSkjCOYe1FmstZ6bve2YvLy/s93tmy+USSUhCEpIIIfAd8/DwwOz9/Z1SCpJIKSGJ9XqNJJxz/MS0bcvs6uoKScQYkYQkJBFjxFrLT0zbtsxub29JKSGJlBKScM5hrcVay09MzplZjJHPz0+894QQCCHwP/7wS/8A4e6nAg+R8LwAAAAASUVORK5CYII='; - await f.fixImageTemplateScale(TINY_PNG, { - defaultImageTemplateScale: 4.0, - fixImageTemplateScale: false, - xScale: 1.5, yScale: 1.5 - }).should.eventually.eql(actual); + const actual = + 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABwUlEQVR4AaXBPUsrQQCG0SeX+cBdkTjwTpG1NPgLpjY/fW1stt4UYmm2cJqwMCsaw70uJJ3CBc9Z/P3Cl+12S9u2tG1L27bEGLm/v2ez2bDZbJDEd/7wS4YT7z3X19fc3Nxwd3dHXdd47xnHkefnZ8ZxpKoq6rqmqiqMMcwMJ1VV0TQN0zThnOPj44O6rsk503UdkmiahqZpWK1WGGOYGU7quqZpGqy1SCLnTM6Z19dXcs5IYpomrLVI4uLigpnhpKoqVqsVkjgcDjw9PdF1HTlnuq5DEs45JHE4HDgznByPR97e3pimiVIK4zhyPB7x3hNCIITA5eUl3nsWiwVnhpNSCsMwsNvtGIaB/X5PKQVJpJSQxHq9RhLOOc4MJ9M0sdvt2G639H3PTBIxRiQhCUnEGLHWcmY4KaUwDAN93/P4+MhyuSSlhCRSSkjCOYe1FmstZ6bve2YvLy/s93tmy+USSUhCEpIIIfAd8/DwwOz9/Z1SCpJIKSGJ9XqNJJxz/MS0bcvs6uoKScQYkYQkJBFjxFrLT0zbtsxub29JKSGJlBKScM5hrcVay09MzplZjJHPz0+894QQCCHwP/7wS/8A4e6nAg+R8LwAAAAASUVORK5CYII='; + await f + .fixImageTemplateScale(TINY_PNG, { + defaultImageTemplateScale: 4.0, + fixImageTemplateScale: false, + xScale: 1.5, + yScale: 1.5, + }) + .should.eventually.eql(actual); }); it('should not fix template size scale because of ignoreDefaultImageTemplateScale', async function () { - await f.fixImageTemplateScale(TINY_PNG, { - defaultImageTemplateScale: 4.0, - ignoreDefaultImageTemplateScale: true, - }).should.eventually.eql(TINY_PNG); + await f + .fixImageTemplateScale(TINY_PNG, { + defaultImageTemplateScale: 4.0, + ignoreDefaultImageTemplateScale: true, + }) + .should.eventually.eql(TINY_PNG); }); it('should ignore defaultImageTemplateScale to fix template size scale because of ignoreDefaultImageTemplateScale', async function () { - const actual = 'iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAWElEQVR4AU3BQRWAQAhAwa/PGBsEgrC16AFBKEIPXW7OXO+Rmey9iQjMjHFzrLUwM7qbqmLcHKpKRFBVuDvj4agq3B1VRUQYT2bS3QwRQVUZF/CaGRHB3wc1vSZbHO5+BgAAAABJRU5ErkJggg=='; - await f.fixImageTemplateScale(TINY_PNG, { - defaultImageTemplateScale: 4.0, - ignoreDefaultImageTemplateScale: true, - fixImageTemplateScale: true, - xScale: 1.5, yScale: 1.5 - }).should.eventually.eql(actual); + const actual = + 'iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAWElEQVR4AU3BQRWAQAhAwa/PGBsEgrC16AFBKEIPXW7OXO+Rmey9iQjMjHFzrLUwM7qbqmLcHKpKRFBVuDvj4agq3B1VRUQYT2bS3QwRQVUZF/CaGRHB3wc1vSZbHO5+BgAAAABJRU5ErkJggg=='; + await f + .fixImageTemplateScale(TINY_PNG, { + defaultImageTemplateScale: 4.0, + ignoreDefaultImageTemplateScale: true, + fixImageTemplateScale: true, + xScale: 1.5, + yScale: 1.5, + }) + .should.eventually.eql(actual); }); }); @@ -233,12 +267,10 @@ describe('finding elements by image', function () { it('should not resize the template if it is smaller than the screen', async function () { const screen = TINY_PNG_DIMS.map((n) => n * 2); - await f.ensureTemplateSize(TINY_PNG, ...screen) - .should.eventually.eql(TINY_PNG); + await f.ensureTemplateSize(TINY_PNG, ...screen).should.eventually.eql(TINY_PNG); }); it('should not resize the template if it is the same size as the screen', async function () { - await f.ensureTemplateSize(TINY_PNG, ...TINY_PNG_DIMS) - .should.eventually.eql(TINY_PNG); + await f.ensureTemplateSize(TINY_PNG, ...TINY_PNG_DIMS).should.eventually.eql(TINY_PNG); }); it('should resize the template if it is bigger than the screen', async function () { const screen = TINY_PNG_DIMS.map((n) => n / 2); @@ -261,7 +293,8 @@ describe('finding elements by image', function () { it('should fail if driver does not support getScreenshot', async function () { const d = new BaseDriver(); const f = new ImageElementFinder(d); - await f.getScreenshotForImageFind() + await f + .getScreenshotForImageFind() .should.eventually.be.rejectedWith(/driver does not support/); }); it('should not adjust or verify screenshot if asked not to by settings', async function () { @@ -283,12 +316,12 @@ describe('finding elements by image', function () { const screenshotObj = await imageUtil.getJimpImage(b64Screenshot); screenshotObj.bitmap.width.should.eql(screen[0]); screenshotObj.bitmap.height.should.eql(screen[1]); - scale.should.eql({ xScale: 1.5, yScale: 1.5 }); + scale.should.eql({xScale: 1.5, yScale: 1.5}); }); it('should return scaled screenshot with different aspect ratio if not matching screen aspect ratio', async function () { // try first with portrait screen, screen = 8 x 12 let screen = [TINY_PNG_DIMS[0] * 2, TINY_PNG_DIMS[1] * 3]; - let expectedScale = { xScale: 2.67, yScale: 4 }; + let expectedScale = {xScale: 2.67, yScale: 4}; const {b64Screenshot, scale} = await f.getScreenshotForImageFind(...screen); b64Screenshot.should.not.eql(TINY_PNG); @@ -300,9 +333,11 @@ describe('finding elements by image', function () { // then with landscape screen, screen = 12 x 8 screen = [TINY_PNG_DIMS[0] * 3, TINY_PNG_DIMS[1] * 2]; - expectedScale = { xScale: 4, yScale: 2.67 }; + expectedScale = {xScale: 4, yScale: 2.67}; - const {b64Screenshot: newScreen, scale: newScale} = await f.getScreenshotForImageFind(...screen); + const {b64Screenshot: newScreen, scale: newScale} = await f.getScreenshotForImageFind( + ...screen + ); newScreen.should.not.eql(TINY_PNG); screenshotObj = await imageUtil.getJimpImage(newScreen); screenshotObj.bitmap.width.should.eql(screen[0]); @@ -314,7 +349,7 @@ describe('finding elements by image', function () { it('should return scaled screenshot with different aspect ratio if not matching screen aspect ratio with fixImageTemplateScale', async function () { // try first with portrait screen, screen = 8 x 12 let screen = [TINY_PNG_DIMS[0] * 2, TINY_PNG_DIMS[1] * 3]; - let expectedScale = { xScale: 2.67, yScale: 4 }; + let expectedScale = {xScale: 2.67, yScale: 4}; const {b64Screenshot, scale} = await f.getScreenshotForImageFind(...screen); b64Screenshot.should.not.eql(TINY_PNG); @@ -324,14 +359,22 @@ describe('finding elements by image', function () { scale.xScale.toFixed(2).should.eql(expectedScale.xScale.toString()); scale.yScale.should.eql(expectedScale.yScale); // 8 x 12 stretched TINY_PNG - await f.fixImageTemplateScale(b64Screenshot, {fixImageTemplateScale: true, scale}) - .should.eventually.eql('iVBORw0KGgoAAAANSUhEUgAAAAgAAAAMCAYAAABfnvydAAAAJ0lEQVR4AYXBAQEAIACDMKR/p0fTBrKdbZcPCRIkSJAgQYIECRIkPAzBA1TpeNwZAAAAAElFTkSuQmCC'); + await f + .fixImageTemplateScale(b64Screenshot, { + fixImageTemplateScale: true, + scale, + }) + .should.eventually.eql( + 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAMCAYAAABfnvydAAAAJ0lEQVR4AYXBAQEAIACDMKR/p0fTBrKdbZcPCRIkSJAgQYIECRIkPAzBA1TpeNwZAAAAAElFTkSuQmCC' + ); // then with landscape screen, screen = 12 x 8 screen = [TINY_PNG_DIMS[0] * 3, TINY_PNG_DIMS[1] * 2]; - expectedScale = { xScale: 4, yScale: 2.67 }; + expectedScale = {xScale: 4, yScale: 2.67}; - const {b64Screenshot: newScreen, scale: newScale} = await f.getScreenshotForImageFind(...screen); + const {b64Screenshot: newScreen, scale: newScale} = await f.getScreenshotForImageFind( + ...screen + ); newScreen.should.not.eql(TINY_PNG); screenshotObj = await imageUtil.getJimpImage(newScreen); screenshotObj.bitmap.width.should.eql(screen[0]); @@ -339,8 +382,11 @@ describe('finding elements by image', function () { newScale.xScale.should.eql(expectedScale.xScale); newScale.yScale.toFixed(2).should.eql(expectedScale.yScale.toString()); // 12 x 8 stretched TINY_PNG - await f.fixImageTemplateScale(newScreen, {fixImageTemplateScale: true, scale}) - .should.eventually.eql('iVBORw0KGgoAAAANSUhEUgAAAAwAAAAICAYAAADN5B7xAAAAI0lEQVR4AZXBAQEAMAyDMI5/T5W2ayB5245AIokkkkgiiST6+W4DTLyo5PUAAAAASUVORK5CYII='); + await f + .fixImageTemplateScale(newScreen, {fixImageTemplateScale: true, scale}) + .should.eventually.eql( + 'iVBORw0KGgoAAAANSUhEUgAAAAwAAAAICAYAAADN5B7xAAAAI0lEQVR4AZXBAQEAMAyDMI5/T5W2ayB5245AIokkkkgiiST6+W4DTLyo5PUAAAAASUVORK5CYII=' + ); }); }); }); diff --git a/packages/images-plugin/test/unit/image-element.spec.js b/packages/images-plugin/test/unit/image-element.spec.js index 62a93efee..e06dd2037 100644 --- a/packages/images-plugin/test/unit/image-element.spec.js +++ b/packages/images-plugin/test/unit/image-element.spec.js @@ -1,8 +1,8 @@ import _ from 'lodash'; import BaseDriver from '@appium/base-driver'; import ImageElementFinder from '../../lib/finder'; -import { getImgElFromArgs } from '../../lib/plugin'; -import ImageElement, { IMAGE_ELEMENT_PREFIX } from '../../lib/image-element'; +import {getImgElFromArgs} from '../../lib/plugin'; +import ImageElement, {IMAGE_ELEMENT_PREFIX} from '../../lib/image-element'; import sinon from 'sinon'; const defRect = {x: 100, y: 110, width: 50, height: 25}; @@ -40,7 +40,7 @@ describe('ImageElement', function () { const el = new ImageElement(defTemplate, defRect); el.center.should.eql({ x: defRect.x + defRect.width / 2, - y: defRect.y + defRect.height / 2 + y: defRect.y + defRect.height / 2, }); }); }); @@ -82,14 +82,14 @@ describe('ImageElement', function () { // we need to check for staleness if explicitly requested to do so await d.settings.update({ checkForImageElementStaleness: true, - autoUpdateImageElementPosition: false + autoUpdateImageElementPosition: false, }); await el.click(d).should.be.rejectedWith(/no longer attached/); // and also if we are updating the element position await d.settings.update({ checkForImageElementStaleness: false, - autoUpdateImageElementPosition: true + autoUpdateImageElementPosition: true, }); await el.click(d).should.be.rejectedWith(/no longer attached/); }); @@ -183,49 +183,61 @@ describe('ImageElement', function () { }); it('should reject executions for unsupported commands', async function () { - await ImageElement.execute(driver, imgEl, 'foobar') - .should.be.rejectedWith(/not yet been implemented/); + await ImageElement.execute(driver, imgEl, 'foobar').should.be.rejectedWith( + /not yet been implemented/ + ); }); it('should get displayed status of element', async function () { - await ImageElement.execute(driver, imgEl, 'elementDisplayed') - .should.eventually.be.true; + await ImageElement.execute(driver, imgEl, 'elementDisplayed').should.eventually.be.true; }); it('should get size of element', async function () { - await ImageElement.execute(driver, imgEl, 'getSize') - .should.eventually.eql({width: defRect.width, height: defRect.height}); + await ImageElement.execute(driver, imgEl, 'getSize').should.eventually.eql({ + width: defRect.width, + height: defRect.height, + }); }); it('should get location of element', async function () { - await ImageElement.execute(driver, imgEl, 'getLocation') - .should.eventually.eql({x: defRect.x, y: defRect.y}); + await ImageElement.execute(driver, imgEl, 'getLocation').should.eventually.eql({ + x: defRect.x, + y: defRect.y, + }); }); it('should get location in view of element', async function () { - await ImageElement.execute(driver, imgEl, 'getLocation') - .should.eventually.eql({x: defRect.x, y: defRect.y}); + await ImageElement.execute(driver, imgEl, 'getLocation').should.eventually.eql({ + x: defRect.x, + y: defRect.y, + }); }); it('should get rect of element', async function () { - await ImageElement.execute(driver, imgEl, 'getElementRect') - .should.eventually.eql(defRect); + await ImageElement.execute(driver, imgEl, 'getElementRect').should.eventually.eql(defRect); }); it('should get score of element', async function () { - await ImageElement.execute(driver, imgEl, 'getAttribute', 'score') - .should.eventually.eql(0); + await ImageElement.execute(driver, imgEl, 'getAttribute', 'score').should.eventually.eql(0); }); it('should get visual of element', async function () { - await ImageElement.execute(driver, imgEl, 'getAttribute', 'visual') - .should.eventually.eql('aGFwcHkgdGVzdGluZw=='); + await ImageElement.execute(driver, imgEl, 'getAttribute', 'visual').should.eventually.eql( + 'aGFwcHkgdGVzdGluZw==' + ); }); it('should get null as visual of element by default', async function () { const imgElement = new ImageElement(defTemplate, defRect); - await ImageElement.execute(driver, imgElement, 'getAttribute', 'visual') - .should.eventually.eql(null); + await ImageElement.execute( + driver, + imgElement, + 'getAttribute', + 'visual' + ).should.eventually.eql(null); }); it('should not get other attribute', async function () { - await ImageElement.execute(driver, imgEl, 'getAttribute', 'content-desc') - .should.eventually.rejectedWith('Method has not yet been implemented'); + await ImageElement.execute( + driver, + imgEl, + 'getAttribute', + 'content-desc' + ).should.eventually.rejectedWith('Method has not yet been implemented'); }); it('should click element', async function () { - await ImageElement.execute(driver, imgEl, 'click') - .should.eventually.be.true; + await ImageElement.execute(driver, imgEl, 'click').should.eventually.be.true; }); }); }); @@ -234,7 +246,7 @@ describe('image element LRU cache', function () { it('should accept and cache image elements', function () { const el1 = new ImageElement(defTemplate, defRect); const el2 = new ImageElement(defTemplate, defRect); - const cache = (new ImageElementFinder()).imgElCache; + const cache = new ImageElementFinder().imgElCache; cache.set(el1.id, el1); el1.equals(cache.get(el1.id)).should.be.true; _.isUndefined(cache.get(el2.id)).should.be.true; @@ -244,7 +256,7 @@ describe('image element LRU cache', function () { it('once cache reaches max size, should eject image elements', function () { const el1 = new ImageElement(defTemplate, defRect); const el2 = new ImageElement(defTemplate, defRect); - const cache = (new ImageElementFinder(null, defTemplate.length + 1)).imgElCache; + const cache = new ImageElementFinder(null, defTemplate.length + 1).imgElCache; cache.set(el1.id, el1); cache.has(el1.id).should.be.true; cache.set(el2.id, el2); diff --git a/packages/images-plugin/test/unit/plugin.spec.js b/packages/images-plugin/test/unit/plugin.spec.js index be2108fd2..32f763925 100644 --- a/packages/images-plugin/test/unit/plugin.spec.js +++ b/packages/images-plugin/test/unit/plugin.spec.js @@ -1,8 +1,8 @@ -import { ImageElementPlugin, IMAGE_STRATEGY } from '../../lib/plugin'; -import { MATCH_FEATURES_MODE, GET_SIMILARITY_MODE, MATCH_TEMPLATE_MODE } from '../../lib/compare'; +import {ImageElementPlugin, IMAGE_STRATEGY} from '../../lib/plugin'; +import {MATCH_FEATURES_MODE, GET_SIMILARITY_MODE, MATCH_TEMPLATE_MODE} from '../../lib/compare'; import BaseDriver from '@appium/base-driver'; -import { W3C_ELEMENT_KEY } from '../../lib/finder'; -import { TEST_IMG_1_B64, TEST_IMG_2_B64, TEST_IMG_2_PART_B64 } from '../fixtures'; +import {W3C_ELEMENT_KEY} from '../../lib/finder'; +import {TEST_IMG_1_B64, TEST_IMG_2_B64, TEST_IMG_2_PART_B64} from '../fixtures'; describe('ImageElementPlugin#handle', function () { const next = () => {}; @@ -11,21 +11,43 @@ describe('ImageElementPlugin#handle', function () { describe('compareImages', function () { this.timeout(6000); it('should compare images via match features mode', async function () { - const res = await p.compareImages(next, driver, MATCH_FEATURES_MODE, TEST_IMG_1_B64, TEST_IMG_2_B64, {}); + const res = await p.compareImages( + next, + driver, + MATCH_FEATURES_MODE, + TEST_IMG_1_B64, + TEST_IMG_2_B64, + {} + ); res.count.should.eql(0); }); it('should compare images via get similarity mode', async function () { - const res = await p.compareImages(next, driver, GET_SIMILARITY_MODE, TEST_IMG_1_B64, TEST_IMG_2_B64, {}); + const res = await p.compareImages( + next, + driver, + GET_SIMILARITY_MODE, + TEST_IMG_1_B64, + TEST_IMG_2_B64, + {} + ); res.score.should.be.above(0.2); }); it('should compare images via match template mode', async function () { - const res = await p.compareImages(next, driver, MATCH_TEMPLATE_MODE, TEST_IMG_1_B64, TEST_IMG_2_B64, {}); + const res = await p.compareImages( + next, + driver, + MATCH_TEMPLATE_MODE, + TEST_IMG_1_B64, + TEST_IMG_2_B64, + {} + ); res.rect.height.should.be.above(0); res.rect.width.should.be.above(0); res.score.should.be.above(0.2); }); it('should throw an error if comparison mode is not supported', async function () { - await p.compareImages(next, driver, 'some mode', '', '') + await p + .compareImages(next, driver, 'some mode', '', '') .should.eventually.be.rejectedWith(/comparison mode is unknown/); }); }); @@ -67,19 +89,23 @@ describe('ImageElementPlugin#handle', function () { }); it('should click on the screen coords of the middle of the element', async function () { let action = null; - driver.performActions = (a) => { action = a; }; + driver.performActions = (a) => { + action = a; + }; await p.handle(next, driver, 'click', elId); - action.should.eql([{ - type: 'pointer', - id: 'mouse', - parameters: {pointerType: 'touch'}, - actions: [ - {type: 'pointerMove', x: 24, y: 40, duration: 0}, - {type: 'pointerDown', button: 0}, - {type: 'pause', duration: 125}, - {type: 'pointerUp', button: 0}, - ] - }]); + action.should.eql([ + { + type: 'pointer', + id: 'mouse', + parameters: {pointerType: 'touch'}, + actions: [ + {type: 'pointerMove', x: 24, y: 40, duration: 0}, + {type: 'pointerDown', button: 0}, + {type: 'pause', duration: 125}, + {type: 'pointerUp', button: 0}, + ], + }, + ]); }); it('should always say the element is displayed', async function () { await p.handle(next, driver, 'elementDisplayed', elId).should.eventually.be.true; @@ -108,16 +134,21 @@ describe('ImageElementPlugin#handle', function () { await p.handle(next, driver, 'getAttribute', 'score', elId).should.eventually.be.above(0.7); }); it('should return the match visualization as the visual attr', async function () { - driver.settings = {getSettings: () => ({ - getMatchedImageResult: true, - })}; + driver.settings = { + getSettings: () => ({ + getMatchedImageResult: true, + }), + }; const el = await p.findElement(next, driver, IMAGE_STRATEGY, TEST_IMG_2_PART_B64); elId = el[W3C_ELEMENT_KEY]; - await p.handle(next, driver, 'getAttribute', 'visual', elId).should.eventually.include('iVBOR'); + await p + .handle(next, driver, 'getAttribute', 'visual', elId) + .should.eventually.include('iVBOR'); }); it('should not allow any other attrs', async function () { - await p.handle(next, driver, 'getAttribute', 'rando', elId).should.eventually.be - .rejectedWith(/not yet/i); + await p + .handle(next, driver, 'getAttribute', 'rando', elId) + .should.eventually.be.rejectedWith(/not yet/i); }); }); }); diff --git a/packages/opencv/lib/index.js b/packages/opencv/lib/index.js index 5aa93a48e..c62557c67 100644 --- a/packages/opencv/lib/index.js +++ b/packages/opencv/lib/index.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import Jimp from 'jimp'; -import { Buffer } from 'buffer'; +import {Buffer} from 'buffer'; import B from 'bluebird'; /** @type {any} */ @@ -70,10 +70,12 @@ const DEFAULT_MATCHING_METHOD = 'TM_CCOEFF_NORMED'; * @returns {number} The method value * @throws {Error} if an unsupported method name is given */ -function toMatchingMethod (name) { +function toMatchingMethod(name) { if (!MATCHING_METHODS.includes(name)) { - throw new Error(`The matching method '${name}' is unknown. ` + - `Only the following matching methods are supported: ${MATCHING_METHODS}`); + throw new Error( + `The matching method '${name}' is unknown. ` + + `Only the following matching methods are supported: ${MATCHING_METHODS}` + ); } return cv[name]; } @@ -81,7 +83,7 @@ function toMatchingMethod (name) { /** * Spins until the opencv-bindings module is fully loaded */ -async function initOpenCv () { +async function initOpenCv() { cv = require('opencv-bindings'); while (!cv.getBuildInformation) { await B.delay(500); @@ -91,7 +93,6 @@ async function initOpenCv () { process.removeAllListeners('unhandledRejection'); } - /** * @typedef MatchComputationResult * @property {OpenCVBindings['Mat']} descriptor - OpenCV match descriptor @@ -109,14 +110,14 @@ async function initOpenCv () { * * @returns {MatchComputationResult} */ -function detectAndCompute (img, detector) { +function detectAndCompute(img, detector) { const keyPoints = new cv.KeyPointVector(); const descriptor = new cv.Mat(); detector.detect(img, keyPoints); detector.compute(img, keyPoints, descriptor); return { keyPoints, - descriptor + descriptor, }; } @@ -127,13 +128,13 @@ function detectAndCompute (img, detector) { * @returns {Rect} The matching bounding rect or a zero rect if no match * can be found. */ -function calculateMatchedRect (matchedPoints) { +function calculateMatchedRect(matchedPoints) { if (matchedPoints.length < 2) { return { x: 0, y: 0, width: 0, - height: 0 + height: 0, }; } @@ -155,7 +156,7 @@ function calculateMatchedRect (matchedPoints) { x: topLeftPoint.x, y: topLeftPoint.y, width: bottomRightPoint.x - topLeftPoint.x, - height: bottomRightPoint.y - topLeftPoint.y + height: bottomRightPoint.y - topLeftPoint.y, }; } @@ -167,7 +168,7 @@ function calculateMatchedRect (matchedPoints) { * * @returns {cv.Mat} The same image with the rectangle on it */ -function highlightRegion (mat, region) { +function highlightRegion(mat, region) { if (region.width <= 0 || region.height <= 0) { return; } @@ -227,27 +228,34 @@ function highlightRegion (mat, region) { * @returns {MatchingResult} Maching result * @throws {Error} If `detectorName` value is unknown. */ -async function getImagesMatches (img1Data, img2Data, options = {}) { +async function getImagesMatches(img1Data, img2Data, options = {}) { await initOpenCv(); let img1, img2, detector, result1, result2, matcher, matchesVec; try { - const {detectorName = 'ORB', visualize = false, - goodMatchesFactor, matchFunc = 'BruteForce'} = options; + const { + detectorName = 'ORB', + visualize = false, + goodMatchesFactor, + matchFunc = 'BruteForce', + } = options; if (!_.includes(_.keys(AVAILABLE_DETECTORS), detectorName)) { - throw new Error(`'${detectorName}' detector is unknown. ` + - `Only ${JSON.stringify(_.keys(AVAILABLE_DETECTORS))} detectors are supported.`); + throw new Error( + `'${detectorName}' detector is unknown. ` + + `Only ${JSON.stringify(_.keys(AVAILABLE_DETECTORS))} detectors are supported.` + ); } if (!_.includes(_.keys(AVAILABLE_MATCHING_FUNCTIONS), matchFunc)) { - throw new Error(`'${matchFunc}' matching function is unknown. ` + - `Only ${JSON.stringify(_.keys(AVAILABLE_MATCHING_FUNCTIONS))} matching functions are supported.`); + throw new Error( + `'${matchFunc}' matching function is unknown. ` + + `Only ${JSON.stringify( + _.keys(AVAILABLE_MATCHING_FUNCTIONS) + )} matching functions are supported.` + ); } detector = new cv[AVAILABLE_DETECTORS[detectorName]](); - ([img1, img2] = await B.all([ - cvMatFromImage(img1Data), - cvMatFromImage(img2Data) - ])); + [img1, img2] = await B.all([cvMatFromImage(img1Data), cvMatFromImage(img2Data)]); result1 = detectAndCompute(img1, detector); result2 = detectAndCompute(img2, detector); matcher = new cv.DescriptorMatcher(AVAILABLE_MATCHING_FUNCTIONS[matchFunc]); @@ -256,8 +264,10 @@ async function getImagesMatches (img1Data, img2Data, options = {}) { matcher.match(result1.descriptor, result2.descriptor, matchesVec); const totalCount = matchesVec.size(); if (totalCount < 1) { - throw new Error(`Could not find any matches between images. Double-check orientation, ` + - `resolution, or use another detector or matching function.`); + throw new Error( + `Could not find any matches between images. Double-check orientation, ` + + `resolution, or use another detector or matching function.` + ); } for (let i = 0; i < totalCount; i++) { matches.push(matchesVec.get(i)); @@ -272,8 +282,9 @@ async function getImagesMatches (img1Data, img2Data, options = {}) { const distances = matches.map((match) => match.distance); const minDistance = _.min(distances); const maxDistance = _.max(distances); - matches = matches - .filter((match) => goodMatchesFactor(match.distance, minDistance, maxDistance)); + matches = matches.filter((match) => + goodMatchesFactor(match.distance, minDistance, maxDistance) + ); } else { if (matches.length > goodMatchesFactor) { matches = matches @@ -286,7 +297,7 @@ async function getImagesMatches (img1Data, img2Data, options = {}) { const extractPoint = (keyPoints, indexPropertyName) => (match) => { const {pt, point} = keyPoints.get(match[indexPropertyName]); // https://github.com/justadudewhohacks/opencv4nodejs/issues/584 - return (pt || point); + return pt || point; }; const points1 = matches.map(extractPoint(result1.keyPoints, 'queryIdx')); const rect1 = calculateMatchedRect(points1); @@ -308,13 +319,21 @@ async function getImagesMatches (img1Data, img2Data, options = {}) { } const visualization = new cv.Mat(); const color = new cv.Scalar(0, 255, 0, 255); - cv.drawMatches(img1, result1.keyPoints, img2, result2.keyPoints, goodMatchesVec, visualization, color); + cv.drawMatches( + img1, + result1.keyPoints, + img2, + result2.keyPoints, + goodMatchesVec, + visualization, + color + ); highlightRegion(visualization, rect1); highlightRegion(visualization, { x: img1.cols + rect2.x, y: rect2.y, width: rect2.width, - height: rect2.height + height: rect2.height, }); result.visualization = await jimpImgFromCvMat(visualization).getBufferAsync(Jimp.MIME_PNG); } @@ -322,8 +341,15 @@ async function getImagesMatches (img1Data, img2Data, options = {}) { return result; } finally { try { - img1.delete(); img2.delete(); detector.delete(); result1.keyPoints.delete(); result1.descriptor.delete(); - result2.keyPoints.delete(); result2.descriptor.delete(); matcher.delete(); matchesVec.delete(); + img1.delete(); + img2.delete(); + detector.delete(); + result1.keyPoints.delete(); + result1.descriptor.delete(); + result2.keyPoints.delete(); + result2.descriptor.delete(); + matcher.delete(); + matchesVec.delete(); } catch (ign) {} } } @@ -364,23 +390,19 @@ async function getImagesMatches (img1Data, img2Data, options = {}) { * @returns {SimilarityResult} The calculation result * @throws {Error} If the given images have different resolution. */ -async function getImagesSimilarity (img1Data, img2Data, options = {}) { +async function getImagesSimilarity(img1Data, img2Data, options = {}) { await initOpenCv(); - const { - method = DEFAULT_MATCHING_METHOD, - visualize = false, - } = options; + const {method = DEFAULT_MATCHING_METHOD, visualize = false} = options; let template, reference, matched; try { - ([template, reference] = await B.all([ - cvMatFromImage(img1Data), - cvMatFromImage(img2Data) - ])); + [template, reference] = await B.all([cvMatFromImage(img1Data), cvMatFromImage(img2Data)]); if (template.rows !== reference.rows || template.cols !== reference.cols) { - throw new Error('Both images are expected to have the same size in order to ' + - 'calculate the similarity score.'); + throw new Error( + 'Both images are expected to have the same size in order to ' + + 'calculate the similarity score.' + ); } template.convertTo(template, cv.CV_8UC3); reference.convertTo(reference, cv.CV_8UC3); @@ -389,7 +411,7 @@ async function getImagesSimilarity (img1Data, img2Data, options = {}) { cv.matchTemplate(reference, template, matched, toMatchingMethod(method)); const minMax = cv.minMaxLoc(matched); const result = { - score: minMax.maxVal + score: minMax.maxVal, }; if (visualize) { @@ -417,20 +439,26 @@ async function getImagesSimilarity (img1Data, img2Data, options = {}) { x: reference.cols + boundingRect.x, y: boundingRect.y, width: boundingRect.width, - height: boundingRect.height + height: boundingRect.height, }); } result.visualization = await jimpImgFromCvMat(resultMat).getBufferAsync(Jimp.MIME_PNG); } finally { try { - bothImages.delete(); resultMat.delete(); mask.delete(); contours.delete(); hierarchy.delete(); + bothImages.delete(); + resultMat.delete(); + mask.delete(); + contours.delete(); + hierarchy.delete(); } catch (ign) {} } } return result; } finally { try { - template.delete(); reference.delete(); matched.delete(); + template.delete(); + reference.delete(); + matched.delete(); } catch (ign) {} } } @@ -485,7 +513,7 @@ async function getImagesSimilarity (img1Data, img2Data, options = {}) { * @returns {OccurrenceResult} * @throws {Error} If no occurrences of the partial image can be found in the full image */ -async function getImageOccurrence (fullImgData, partialImgData, options = {}) { +async function getImageOccurrence(fullImgData, partialImgData, options = {}) { await initOpenCv(); const { @@ -499,10 +527,10 @@ async function getImageOccurrence (fullImgData, partialImgData, options = {}) { let fullImg, partialImg, matched; try { - ([fullImg, partialImg] = await B.all([ + [fullImg, partialImg] = await B.all([ cvMatFromImage(fullImgData), - cvMatFromImage(partialImgData) - ])); + cvMatFromImage(partialImgData), + ]); matched = new cv.Mat(); const results = []; let visualization = null; @@ -528,10 +556,11 @@ async function getImageOccurrence (fullImgData, partialImgData, options = {}) { results.push({ score, rect: { - x, y, + x, + y, width: partialImg.cols, - height: partialImg.rows - } + height: partialImg.rows, + }, }); } } else if (minMax.maxVal >= threshold) { @@ -539,22 +568,26 @@ async function getImageOccurrence (fullImgData, partialImgData, options = {}) { results.push({ score: minMax.maxVal, rect: { - x, y, + x, + y, width: partialImg.cols, - height: partialImg.rows - } + height: partialImg.rows, + }, }); } if (_.isEmpty(results)) { // Below error message, `Cannot find any occurrences` is referenced in find by image - throw new Error(`Match threshold: ${threshold}. Highest match value ` + - `found was ${minMax.maxVal}`); + throw new Error( + `Match threshold: ${threshold}. Highest match value ` + `found was ${minMax.maxVal}` + ); } } catch (e) { // Below error message, `Cannot find any occurrences` is referenced in find by image - throw new Error(`Cannot find any occurrences of the partial image in the full image. ` + - `Original error: ${e.message}`); + throw new Error( + `Cannot find any occurrences of the partial image in the full image. ` + + `Original error: ${e.message}` + ); } if (visualize) { @@ -565,7 +598,9 @@ async function getImageOccurrence (fullImgData, partialImgData, options = {}) { highlightRegion(singleHighlightedImage, result.rect); highlightRegion(fullHighlightedImage, result.rect); - result.visualization = await jimpImgFromCvMat(singleHighlightedImage).getBufferAsync(Jimp.MIME_PNG); + result.visualization = await jimpImgFromCvMat(singleHighlightedImage).getBufferAsync( + Jimp.MIME_PNG + ); } visualization = await jimpImgFromCvMat(fullHighlightedImage).getBufferAsync(Jimp.MIME_PNG); } @@ -573,11 +608,13 @@ async function getImageOccurrence (fullImgData, partialImgData, options = {}) { rect: results[0].rect, score: results[0].score, visualization, - multiple: results + multiple: results, }; } finally { try { - fullImg.delete(); partialImg.delete(); matched.delete(); + fullImg.delete(); + partialImg.delete(); + matched.delete(); } catch (ign) {} } } @@ -588,11 +625,11 @@ async function getImageOccurrence (fullImgData, partialImgData, options = {}) { * @param {cv.Mat} mat the image matrix * @return {Jimp} the Jimp image */ -function jimpImgFromCvMat (mat) { +function jimpImgFromCvMat(mat) { return new Jimp({ width: mat.cols, height: mat.rows, - data: Buffer.from(mat.data) + data: Buffer.from(mat.data), }); } @@ -602,7 +639,7 @@ function jimpImgFromCvMat (mat) { * @param {Buffer} img the image data buffer * @return {cv.Mat} the opencv matrix */ -async function cvMatFromImage (img) { +async function cvMatFromImage(img) { const jimpImg = await Jimp.read(img); return cv.matFromImageData(jimpImg.bitmap); } @@ -615,7 +652,7 @@ async function cvMatFromImage (img) { * consider an element being a neighbour of an existing match * @return {Array} the filtered array of matched points */ -function filterNearMatches (nonZeroMatchResults, matchNeighbourThreshold) { +function filterNearMatches(nonZeroMatchResults, matchNeighbourThreshold) { return nonZeroMatchResults.reduce((acc, element) => { if (!acc.some((match) => distance(match, element) <= matchNeighbourThreshold)) { acc.push(element); @@ -631,18 +668,13 @@ function filterNearMatches (nonZeroMatchResults, matchNeighbourThreshold) { * @param {Point} point2 The second point * @return {number} the distance */ -function distance (point1, point2) { - const a2 = Math.pow((point1.x - point2.x), 2); - const b2 = Math.pow((point1.y - point2.y), 2); +function distance(point1, point2) { + const a2 = Math.pow(point1.x - point2.x, 2); + const b2 = Math.pow(point1.y - point2.y, 2); return Math.sqrt(a2 + b2); } -export { - getImagesMatches, - getImagesSimilarity, - getImageOccurrence, - initOpenCv -}; +export {getImagesMatches, getImagesSimilarity, getImageOccurrence, initOpenCv}; /** * @typedef OpenCVBindings diff --git a/packages/opencv/test/e2e/opencv.e2e.spec.js b/packages/opencv/test/e2e/opencv.e2e.spec.js index 9866f978e..5b2906e22 100644 --- a/packages/opencv/test/e2e/opencv.e2e.spec.js +++ b/packages/opencv/test/e2e/opencv.e2e.spec.js @@ -1,8 +1,6 @@ -import { - getImagesMatches, getImagesSimilarity, getImageOccurrence -} from '../../lib'; +import {getImagesMatches, getImagesSimilarity, getImageOccurrence} from '../../lib'; import path from 'path'; -import { fs } from '@appium/support'; +import {fs} from '@appium/support'; const FIXTURES_ROOT = path.resolve(__dirname, 'images'); @@ -39,16 +37,22 @@ describe('OpenCV helpers', function () { }); it('should visualize matches between two images', async function () { - const {visualization} = await getImagesMatches(fullImage, fullImage, {visualize: true}); + const {visualization} = await getImagesMatches(fullImage, fullImage, { + visualize: true, + }); visualization.should.not.be.empty; }); it('should visualize matches between two images and apply goodMatchesFactor', async function () { - const {visualization, points1, rect1, points2, rect2} = await getImagesMatches(rotatedImage, originalImage, { - visualize: true, - matchFunc: 'BruteForceHamming', - goodMatchesFactor: 40 - }); + const {visualization, points1, rect1, points2, rect2} = await getImagesMatches( + rotatedImage, + originalImage, + { + visualize: true, + matchFunc: 'BruteForceHamming', + goodMatchesFactor: 40, + } + ); visualization.should.not.be.empty; points1.length.should.be.above(4); rect1.x.should.be.above(0); @@ -70,7 +74,9 @@ describe('OpenCV helpers', function () { }); it('should visualize the similarity between two images', async function () { - const {visualization} = await getImagesSimilarity(originalImage, changedImage, {visualize: true}); + const {visualization} = await getImagesSimilarity(originalImage, changedImage, { + visualize: true, + }); visualization.should.not.be.empty; }); }); @@ -86,8 +92,9 @@ describe('OpenCV helpers', function () { }); it('should reject matches that fall below a threshold', async function () { - await getImageOccurrence(fullImage, partialImage, {threshold: 1.0}) - .should.eventually.be.rejectedWith(/threshold/); + await getImageOccurrence(fullImage, partialImage, { + threshold: 1.0, + }).should.eventually.be.rejectedWith(/threshold/); }); it('should visualize the partial image position in the full image', async function () { @@ -97,7 +104,10 @@ describe('OpenCV helpers', function () { describe('multiple', function () { it('should return matches in the full image', async function () { - const { multiple } = await getImageOccurrence(originalImage, numberImage, {threshold: 0.8, multiple: true}); + const {multiple} = await getImageOccurrence(originalImage, numberImage, { + threshold: 0.8, + multiple: true, + }); multiple.length.should.be.eq(3); for (const result of multiple) { @@ -110,12 +120,18 @@ describe('OpenCV helpers', function () { }); it('should reject matches that fall below a threshold', async function () { - const { multiple } = await getImageOccurrence(originalImage, numberImage, {threshold: 1.0, multiple: true}); + const {multiple} = await getImageOccurrence(originalImage, numberImage, { + threshold: 1.0, + multiple: true, + }); multiple.length.should.be.eq(1); }); it('should visualize the partial image position in the full image', async function () { - const { multiple } = await getImageOccurrence(originalImage, numberImage, {visualize: true, multiple: true}); + const {multiple} = await getImageOccurrence(originalImage, numberImage, { + visualize: true, + multiple: true, + }); for (const result of multiple) { result.visualization.should.not.be.empty; diff --git a/packages/opencv/test/unit/opencv.spec.js b/packages/opencv/test/unit/opencv.spec.js index 62a093fac..85c7975b5 100644 --- a/packages/opencv/test/unit/opencv.spec.js +++ b/packages/opencv/test/unit/opencv.spec.js @@ -1,4 +1,4 @@ -import { initOpenCv } from '../../lib'; +import {initOpenCv} from '../../lib'; describe('OpenCV', function () { it('should initialize opencv library', async function () { diff --git a/packages/relaxed-caps-plugin/lib/plugin.js b/packages/relaxed-caps-plugin/lib/plugin.js index 238c2f431..7f651c819 100644 --- a/packages/relaxed-caps-plugin/lib/plugin.js +++ b/packages/relaxed-caps-plugin/lib/plugin.js @@ -19,7 +19,7 @@ const VENDOR_PREFIX = 'appium'; const HAS_VENDOR_PREFIX_RE = /^.+:/; export default class RelaxedCapsPlugin extends BasePlugin { - transformCaps (caps) { + transformCaps(caps) { const newCaps = {}; // if this doesn't look like a caps object just return it @@ -39,13 +39,15 @@ export default class RelaxedCapsPlugin extends BasePlugin { } } if (adjustedKeys.length) { - this.logger.info(`Adjusted keys to conform to capability prefix requirements: ` + - JSON.stringify(adjustedKeys)); + this.logger.info( + `Adjusted keys to conform to capability prefix requirements: ` + + JSON.stringify(adjustedKeys) + ); } return newCaps; } - async createSession (next, driver, jwpDesCaps, jwpReqCaps, caps) { + async createSession(next, driver, jwpDesCaps, jwpReqCaps, caps) { const newCaps = {}; if (_.isArray(caps.firstMatch)) { newCaps.firstMatch = caps.firstMatch.map(this.transformCaps.bind(this)); diff --git a/packages/relaxed-caps-plugin/test/unit/plugin.spec.js b/packages/relaxed-caps-plugin/test/unit/plugin.spec.js index 7d37ee2ff..0fbccd47d 100644 --- a/packages/relaxed-caps-plugin/test/unit/plugin.spec.js +++ b/packages/relaxed-caps-plugin/test/unit/plugin.spec.js @@ -64,7 +64,6 @@ describe('relaxed caps plugin', function () { it('should not transform already prefixed caps', function () { rcp.transformCaps(VENDOR_CAPS).should.eql(ADJUSTED_VENDOR_CAPS); }); - }); describe('#createSession', function () { @@ -83,7 +82,9 @@ describe('relaxed caps plugin', function () { it('should work with multiple firstMatch', async function () { const mock = sandbox.mock(driver); const w3c = {firstMatch: [MIXED_CAPS, STD_CAPS, MIXED_CAPS]}; - const w3cAdjusted = {firstMatch: [ADJUSTED_CAPS, STD_CAPS, ADJUSTED_CAPS]}; + const w3cAdjusted = { + firstMatch: [ADJUSTED_CAPS, STD_CAPS, ADJUSTED_CAPS], + }; mock.expects('createSession').once().withExactArgs(null, null, w3cAdjusted); await rcp.createSession(next, driver, null, null, w3c); mock.verify(); @@ -101,7 +102,10 @@ describe('relaxed caps plugin', function () { it('should work with alwaysMatch and firstMatch', async function () { const mock = sandbox.mock(driver); const w3c = {alwaysMatch: MIXED_CAPS, firstMatch: [MIXED_CAPS]}; - const w3cAdjusted = {alwaysMatch: ADJUSTED_CAPS, firstMatch: [ADJUSTED_CAPS]}; + const w3cAdjusted = { + alwaysMatch: ADJUSTED_CAPS, + firstMatch: [ADJUSTED_CAPS], + }; mock.expects('createSession').once().withExactArgs(null, null, w3cAdjusted); await rcp.createSession(next, driver, null, null, w3c); mock.verify(); diff --git a/packages/schema/lib/appium-config-schema.js b/packages/schema/lib/appium-config-schema.js index bf9f3435a..cc4af0944 100644 --- a/packages/schema/lib/appium-config-schema.js +++ b/packages/schema/lib/appium-config-schema.js @@ -4,7 +4,7 @@ * This defines _both_ what the CLI supports and what the config files support. */ -export const AppiumConfigJsonSchema = /** @type {const} */({ +export const AppiumConfigJsonSchema = /** @type {const} */ ({ $schema: 'http://json-schema.org/draft-07/schema', additionalProperties: false, description: 'A schema for Appium configuration files', @@ -14,8 +14,7 @@ export const AppiumConfigJsonSchema = /** @type {const} */({ description: 'Configuration when running Appium as a server', properties: { address: { - $comment: - 'I think hostname covers both DNS and IPv4...could be wrong', + $comment: 'I think hostname covers both DNS and IPv4...could be wrong', appiumCliAliases: ['a'], default: '0.0.0.0', description: 'IP address to listen on', @@ -67,8 +66,7 @@ export const AppiumConfigJsonSchema = /** @type {const} */({ }, 'debug-log-spacing': { default: false, - description: - 'Add exaggerated spacing in logs to help with visual inspection', + description: 'Add exaggerated spacing in logs to help with visual inspection', title: 'debug-log-spacing config', type: 'boolean', }, @@ -96,7 +94,7 @@ export const AppiumConfigJsonSchema = /** @type {const} */({ driver: { description: 'Driver-specific configuration. Keys should correspond to driver package names', - properties: /** @type {Record} */({}), + properties: /** @type {Record} */ ({}), title: 'driver config', type: 'object', }, @@ -174,15 +172,13 @@ export const AppiumConfigJsonSchema = /** @type {const} */({ }, 'long-stacktrace': { default: false, - description: - 'Add long stack traces to log entries. Recommended for debugging only.', + description: 'Add long stack traces to log entries. Recommended for debugging only.', title: 'long-stacktrace config', type: 'boolean', }, 'no-perms-check': { default: false, - description: - 'Do not check that needed files are readable and/or writable', + description: 'Do not check that needed files are readable and/or writable', title: 'no-perms-check config', type: 'boolean', }, @@ -197,7 +193,7 @@ export const AppiumConfigJsonSchema = /** @type {const} */({ plugin: { description: 'Plugin-specific configuration. Keys should correspond to plugin package names', - properties: /** @type {Record} */({}), + properties: /** @type {Record} */ ({}), title: 'plugin config', type: 'object', }, @@ -216,7 +212,7 @@ export const AppiumConfigJsonSchema = /** @type {const} */({ 'Disable additional security checks, so it is possible to use some advanced features, provided by drivers supporting this option. Only enable it if all the clients are in the trusted network and it\'s not the case if a client could potentially break out of the session sandbox. Specific features can be overridden by using "deny-insecure"', title: 'relaxed-security config', type: 'boolean', - appiumCliDest: 'relaxedSecurityEnabled' + appiumCliDest: 'relaxedSecurityEnabled', }, 'session-override': { default: false, diff --git a/packages/schema/scripts/generate-schema-json.js b/packages/schema/scripts/generate-schema-json.js index a0a1eba04..e5064623e 100755 --- a/packages/schema/scripts/generate-schema-json.js +++ b/packages/schema/scripts/generate-schema-json.js @@ -36,14 +36,14 @@ const SCHEMA_SRC = path.join(SCHEMA_ROOT, 'build', 'appium-config-schema.js'); */ const OUTPUT_PATH = path.join(OUTPUT_DIR, JSON_FILENAME); -async function write () { +async function write() { /** @type {typeof import('../lib/appium-config-schema').AppiumConfigJsonSchema} */ let schema; try { ({AppiumConfigJsonSchema: schema} = require(SCHEMA_SRC)); } catch (err) { throw new Error( - `${error} Failed to read ${SCHEMA_SRC}; did you execute \`npm run build\` first?`, + `${error} Failed to read ${SCHEMA_SRC}; did you execute \`npm run build\` first?` ); } @@ -54,13 +54,11 @@ async function write () { await writeFile(OUTPUT_PATH, json); console.log(`${info} Wrote JSON schema to ${OUTPUT_PATH}`); } catch (err) { - throw new Error( - `${error} Failed to write JSON schema to ${OUTPUT_PATH}: ${err.message}`, - ); + throw new Error(`${error} Failed to write JSON schema to ${OUTPUT_PATH}: ${err.message}`); } } -async function main () { +async function main() { try { await write(); } catch (err) { diff --git a/packages/support/lib/env.js b/packages/support/lib/env.js index fb18cc93d..6707539f4 100644 --- a/packages/support/lib/env.js +++ b/packages/support/lib/env.js @@ -1,10 +1,10 @@ // @ts-check import _ from 'lodash'; -import { homedir } from 'os'; +import {homedir} from 'os'; import path from 'path'; import pkgDir from 'pkg-dir'; import readPkg from 'read-pkg'; -import { npm } from './npm'; +import {npm} from './npm'; /** * Path to the default `APPIUM_HOME` dir (`~/.appium`). @@ -26,7 +26,7 @@ export const MANIFEST_RELATIVE_PATH = path.join( 'node_modules', '.cache', 'appium', - MANIFEST_BASENAME, + MANIFEST_BASENAME ); /** @@ -45,7 +45,7 @@ export const MANIFEST_RELATIVE_PATH = path.join( * @param {string} cwd * @returns {Promise} */ -export async function hasAppiumDependency (cwd) { +export async function hasAppiumDependency(cwd) { /** * @todo type this * @type {object} @@ -78,7 +78,7 @@ export async function hasAppiumDependency (cwd) { // doing any further checking here may be a fool's errand, because you can pin the version // to a _lot_ of different things (tags, URLs, etc). !version.startsWith('1') && - !version.startsWith('0'), + !version.startsWith('0') ); } @@ -91,9 +91,9 @@ export const readPackageInDir = _.memoize( * @param {string} cwd - Directory ostensibly having a `package.json` * @returns {Promise} */ - async function _readPackageInDir (cwd) { + async function _readPackageInDir(cwd) { return await readPkg({cwd, normalize: true}); - }, + } ); /** @@ -108,7 +108,7 @@ export const resolveAppiumHome = _.memoize( * @param {string} [cwd] - Current working directory. _Must_ be absolute, if specified. * @returns {Promise} */ - async function _resolveAppiumHome (cwd = process.cwd()) { + async function _resolveAppiumHome(cwd = process.cwd()) { if (process.env.APPIUM_HOME) { return process.env.APPIUM_HOME; } @@ -133,10 +133,8 @@ export const resolveAppiumHome = _.memoize( return DEFAULT_APPIUM_HOME; } - return (await hasAppiumDependency(currentPkgDir)) - ? currentPkgDir - : DEFAULT_APPIUM_HOME; - }, + return (await hasAppiumDependency(currentPkgDir)) ? currentPkgDir : DEFAULT_APPIUM_HOME; + } ); /** @@ -150,11 +148,11 @@ export const resolveManifestPath = _.memoize( * @param {string} [appiumHome] - Appium home directory * @returns {Promise} */ - async function _resolveManifestPath (appiumHome) { + async function _resolveManifestPath(appiumHome) { // can you "await" in a default parameter? is that a good idea? appiumHome = appiumHome ?? (await resolveAppiumHome()); return path.join(appiumHome, MANIFEST_RELATIVE_PATH); - }, + } ); /** diff --git a/packages/support/lib/fs.js b/packages/support/lib/fs.js index 14bc66b27..6d9ce186b 100644 --- a/packages/support/lib/fs.js +++ b/packages/support/lib/fs.js @@ -2,7 +2,16 @@ import B from 'bluebird'; import crypto from 'crypto'; -import { close, constants, createReadStream, createWriteStream, promises as fsPromises, read, write, open } from 'fs'; +import { + close, + constants, + createReadStream, + createWriteStream, + promises as fsPromises, + read, + write, + open, +} from 'fs'; import glob from 'glob'; import klaw from 'klaw'; import _ from 'lodash'; @@ -16,9 +25,12 @@ import sanitize from 'sanitize-filename'; import which from 'which'; import log from './logger'; import Timer from './timing'; -import { pluralize } from './util'; +import {pluralize} from './util'; -const ncpAsync = /** @type {(source: string, dest: string, opts: ncp.Options|undefined) => B} */(B.promisify(ncp)); +const ncpAsync = + /** @type {(source: string, dest: string, opts: ncp.Options|undefined) => B} */ ( + B.promisify(ncp) + ); const findRootCached = _.memoize(pkgDir.sync); const fs = { @@ -27,7 +39,7 @@ const fs = { * @param {import('fs').PathLike} path * @returns {Promise} */ - async hasAccess (path) { + async hasAccess(path) { try { await fsPromises.access(path, constants.R_OK); } catch (err) { @@ -40,7 +52,7 @@ const fs = { * Alias for {@linkcode fs.hasAccess} * @param {import('fs').PathLike} path */ - async exists (path) { + async exists(path) { return await fs.hasAccess(path); }, @@ -48,7 +60,9 @@ const fs = { * Remove a directory and all its contents, recursively * @todo Replace with `rm()` from `fs.promises` when Node.js v12 support is dropped. */ - rimraf: /** @type {(dirpath: string, opts?: import('rimraf').Options) => Promise} */(B.promisify(rimrafIdx)), + rimraf: /** @type {(dirpath: string, opts?: import('rimraf').Options) => Promise} */ ( + B.promisify(rimrafIdx) + ), /** * Alias of {@linkcode rimrafIdx.sync} @@ -64,7 +78,7 @@ const fs = { * @returns {Promise} * @see https://nodejs.org/api/fs.html#fspromisesmkdirpath-options */ - async mkdir (filepath, opts = {}) { + async mkdir(filepath, opts = {}) { try { return await fsPromises.mkdir(filepath, opts); } catch (err) { @@ -81,8 +95,8 @@ const fs = { * @see https://npm.im/ncp * @returns {Promise} */ - async copyFile (source, destination, opts = {}) { - if (!await fs.hasAccess(source)) { + async copyFile(source, destination, opts = {}) { + if (!(await fs.hasAccess(source))) { throw new Error(`The file at '${source}' does not exist or is not accessible`); } return await ncpAsync(source, destination, opts); @@ -93,14 +107,14 @@ const fs = { * @param {import('fs').PathLike} filePath * @returns {Promise} */ - async md5 (filePath) { + async md5(filePath) { return await fs.hash(filePath, 'md5'); }, /** * Move a file */ - mv: /** @type {(from: string, to: string, opts?: mv.Options) => B} */(B.promisify(mv)), + mv: /** @type {(from: string, to: string, opts?: mv.Options) => B} */ (B.promisify(mv)), /** * Find path to an executable in system `PATH` @@ -112,7 +126,7 @@ const fs = { * Given a glob pattern, resolve with list of files matching that pattern * @see https://github.com/isaacs/node-glob */ - glob: /** @type {(pattern: string, opts?: glob.IOptions) => B} */(B.promisify(glob)), + glob: /** @type {(pattern: string, opts?: glob.IOptions) => B} */ (B.promisify(glob)), /** * Sanitize a filename @@ -126,12 +140,17 @@ const fs = { * @param {string} [algorithm] * @returns {Promise} */ - async hash (filePath, algorithm = 'sha1') { + async hash(filePath, algorithm = 'sha1') { return await new B((resolve, reject) => { const fileHash = crypto.createHash(algorithm); const readStream = createReadStream(filePath); - readStream.on('error', (e) => reject( - new Error(`Cannot calculate ${algorithm} hash for '${filePath}'. Original error: ${e.message}`))); + readStream.on('error', (e) => + reject( + new Error( + `Cannot calculate ${algorithm} hash for '${filePath}'. Original error: ${e.message}` + ) + ) + ); readStream.on('data', (chunk) => fileHash.update(chunk)); readStream.on('end', () => resolve(fileHash.digest('hex'))); }); @@ -145,7 +164,7 @@ const fs = { * @returns {import('klaw').Walker} * @see https://www.npmjs.com/package/klaw */ - walk (dir, opts) { + walk(dir, opts) { return klaw(dir, opts); }, @@ -154,7 +173,7 @@ const fs = { * @param {import('fs').PathLike} dir * @returns {Promise} */ - async mkdirp (dir) { + async mkdirp(dir) { return await fs.mkdir(dir, {recursive: true}); }, @@ -166,7 +185,8 @@ const fs = { * @throws {Error} If the `dir` parameter contains a path to an invalid folder * @returns {Promise} returns the found path or null if the item was not found */ - async walkDir (dir, recursive, callback) { //eslint-disable-line promise/prefer-await-to-callbacks + async walkDir(dir, recursive, callback) { + //eslint-disable-line promise/prefer-await-to-callbacks let isValidRoot = false; let errMsg = null; try { @@ -175,7 +195,9 @@ const fs = { errMsg = e.message; } if (!isValidRoot) { - throw Error(`'${dir}' is not a valid root directory` + (errMsg ? `. Original error: ${errMsg}` : '')); + throw Error( + `'${dir}' is not a valid root directory` + (errMsg ? `. Original error: ${errMsg}` : '') + ); } let walker; @@ -187,48 +209,51 @@ const fs = { walker = klaw(dir, { depthLimit: recursive ? -1 : 0, }); - walker.on('data', function (item) { - walker.pause(); + walker + .on('data', function (item) { + walker.pause(); - if (!item.stats.isDirectory()) { - fileCount++; - } else { - directoryCount++; - } + if (!item.stats.isDirectory()) { + fileCount++; + } else { + directoryCount++; + } - // eslint-disable-next-line promise/prefer-await-to-callbacks - lastFileProcessed = B.try(async () => await callback(item.path, item.stats.isDirectory())) - .then(function (done = false) { - if (done) { - resolve(item.path); - } else { - walker.resume(); - } - }) - .catch(reject); - }) - .on('error', function (err, item) { - log.warn(`Got an error while walking '${item.path}': ${err.message}`); - // klaw cannot get back from an ENOENT error - if (err.code === 'ENOENT') { - log.warn('All files may not have been accessed'); - reject(err); - } - }) - .on('end', function () { - lastFileProcessed - .then((file) => { - resolve(/** @type {string|undefined} */(file) ?? null); - }) - .catch(function (err) { - log.warn(`Unexpected error: ${err.message}`); + // eslint-disable-next-line promise/prefer-await-to-callbacks + lastFileProcessed = B.try(async () => await callback(item.path, item.stats.isDirectory())) + .then(function (done = false) { + if (done) { + resolve(item.path); + } else { + walker.resume(); + } + }) + .catch(reject); + }) + .on('error', function (err, item) { + log.warn(`Got an error while walking '${item.path}': ${err.message}`); + // klaw cannot get back from an ENOENT error + if (err.code === 'ENOENT') { + log.warn('All files may not have been accessed'); reject(err); - }); - }); + } + }) + .on('end', function () { + lastFileProcessed + .then((file) => { + resolve(/** @type {string|undefined} */ (file) ?? null); + }) + .catch(function (err) { + log.warn(`Unexpected error: ${err.message}`); + reject(err); + }); + }); }).finally(function () { - log.debug(`Traversed ${pluralize('directory', directoryCount, true)} ` + - `and ${pluralize('file', fileCount, true)} ` + - `in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`); + log.debug( + `Traversed ${pluralize('directory', directoryCount, true)} ` + + `and ${pluralize('file', fileCount, true)} ` + + `in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms` + ); if (walker) { walker.destroy(); } @@ -241,7 +266,7 @@ const fs = { * @throws {Error} If there were problems finding or reading a `package.json` file * @returns {object} A parsed `package.json` */ - readPackageJsonFrom (dir, opts = {}) { + readPackageJsonFrom(dir, opts = {}) { const cwd = fs.findRoot(dir); try { return readPkg.sync({...opts, cwd}); @@ -257,7 +282,7 @@ const fs = { * @throws {Error} If there were problems finding the project root * @returns {string} The closeset parent dir containing `package.json` */ - findRoot (dir) { + findRoot(dir) { if (!dir || !path.isAbsolute(dir)) { throw new TypeError('`findRoot()` must be provided a non-empty, absolute path'); } @@ -320,10 +345,10 @@ const fs = { * Use `constants.X_OK` instead. * @deprecated */ - X_OK: constants.X_OK + X_OK: constants.X_OK, }; -export { fs }; +export {fs}; export default fs; /** @@ -332,7 +357,7 @@ export default fs; * @param {string} itemPath The path of the file or folder * @param {boolean} isDirectory Shows if it is a directory or a file * @return {boolean} return true if you want to stop walking -*/ + */ /** * @typedef {import('glob')} glob diff --git a/packages/support/lib/image-util.js b/packages/support/lib/image-util.js index 5193c0f77..c397a0b1f 100644 --- a/packages/support/lib/image-util.js +++ b/packages/support/lib/image-util.js @@ -1,12 +1,12 @@ import _ from 'lodash'; import Jimp from 'jimp'; -import { Buffer } from 'buffer'; -import { PNG } from 'pngjs'; +import {Buffer} from 'buffer'; +import {PNG} from 'pngjs'; import B from 'bluebird'; const BYTES_IN_PIXEL_BLOCK = 4; const SCANLINE_FILTER_METHOD = 4; -const { MIME_JPEG, MIME_PNG, MIME_BMP } = Jimp; +const {MIME_JPEG, MIME_PNG, MIME_BMP} = Jimp; /** * Utility function to get a Jimp image object from buffer or base64 data. Jimp @@ -17,7 +17,7 @@ const { MIME_JPEG, MIME_PNG, MIME_BMP } = Jimp; * string * @returns {Promise} - the jimp image object */ -async function getJimpImage (data) { +async function getJimpImage(data) { return await new B((resolve, reject) => { if (!_.isString(data) && !_.isBuffer(data)) { return reject(new Error('Must initialize jimp object with string or buffer')); @@ -26,21 +26,25 @@ async function getJimpImage (data) { if (_.isString(data)) { data = Buffer.from(data, 'base64'); } - new Jimp(data, + new Jimp( + data, /** * @param {Error?} err * @param {AppiumJimp} imgObj */ - (err, imgObj) => { - if (err) { - return reject(err); + (err, imgObj) => { + if (err) { + return reject(err); + } + if (!imgObj) { + return reject(new Error('Could not create jimp image from that data')); + } + imgObj.getBuffer = B.promisify(imgObj.getBuffer.bind(imgObj), { + context: imgObj, + }); + resolve(imgObj); } - if (!imgObj) { - return reject(new Error('Could not create jimp image from that data')); - } - imgObj.getBuffer = B.promisify(imgObj.getBuffer.bind(imgObj), {context: imgObj}); - resolve(imgObj); - }); + ); }); } @@ -51,7 +55,7 @@ async function getJimpImage (data) { * @param {Region} rect The selected region of image * @return {Promise} base64 encoded string of cropped image */ -async function cropBase64Image (base64Image, rect) { +async function cropBase64Image(base64Image, rect) { const image = await base64ToImage(base64Image); cropImage(image, rect); return await imageToBase64(image); @@ -63,11 +67,12 @@ async function cropBase64Image (base64Image, rect) { * @param {string} base64Image The string with base64 encoded image * @return {Promise} The image object */ -async function base64ToImage (base64Image) { +async function base64ToImage(base64Image) { const imageBuffer = Buffer.from(base64Image, 'base64'); return await new B((resolve, reject) => { const image = new PNG({filterType: SCANLINE_FILTER_METHOD}); - image.parse(imageBuffer, (err, image) => { // eslint-disable-line promise/prefer-await-to-callbacks + image.parse(imageBuffer, (err, image) => { + // eslint-disable-line promise/prefer-await-to-callbacks if (err) { return reject(err); } @@ -82,16 +87,19 @@ async function base64ToImage (base64Image) { * @param {PNG} image The image object * @return {Promise} The string with base64 encoded image */ -async function imageToBase64 (image) { +async function imageToBase64(image) { return await new B((resolve, reject) => { const chunks = []; - image.pack() - .on('data', (chunk) => chunks.push(chunk)).on('end', () => { - resolve(Buffer.concat(chunks).toString('base64')); - }) - .on('error', (err) => { // eslint-disable-line promise/prefer-await-to-callbacks - reject(err); - }); + image + .pack() + .on('data', (chunk) => chunks.push(chunk)) + .on('end', () => { + resolve(Buffer.concat(chunks).toString('base64')); + }) + .on('error', (err) => { + // eslint-disable-line promise/prefer-await-to-callbacks + reject(err); + }); }); } @@ -101,11 +109,15 @@ async function imageToBase64 (image) { * @param {PNG} image The image to mutate by cropping * @param {Region} rect The selected region of image */ -function cropImage (image, rect) { +function cropImage(image, rect) { const imageRect = {width: image.width, height: image.height}; const interRect = getRectIntersection(rect, imageRect); if (interRect.width < rect.width || interRect.height < rect.height) { - throw new Error(`Cannot crop ${JSON.stringify(rect)} from ${JSON.stringify(imageRect)} because the intersection between them was not the size of the rect`); + throw new Error( + `Cannot crop ${JSON.stringify(rect)} from ${JSON.stringify( + imageRect + )} because the intersection between them was not the size of the rect` + ); } const firstVerticalPixel = interRect.top; @@ -130,17 +142,23 @@ function cropImage (image, rect) { return image; } -function getRectIntersection (rect, imageSize) { +function getRectIntersection(rect, imageSize) { const left = rect.left >= imageSize.width ? imageSize.width : rect.left; const top = rect.top >= imageSize.height ? imageSize.height : rect.top; - const width = imageSize.width >= (left + rect.width) ? rect.width : (imageSize.width - left); - const height = imageSize.height >= (top + rect.height) ? rect.height : (imageSize.height - top); + const width = imageSize.width >= left + rect.width ? rect.width : imageSize.width - left; + const height = imageSize.height >= top + rect.height ? rect.height : imageSize.height - top; return {left, top, width, height}; } export { - cropBase64Image, base64ToImage, imageToBase64, cropImage, - getJimpImage, MIME_JPEG, MIME_PNG, MIME_BMP + cropBase64Image, + base64ToImage, + imageToBase64, + cropImage, + getJimpImage, + MIME_JPEG, + MIME_PNG, + MIME_BMP, }; /** diff --git a/packages/support/lib/index.js b/packages/support/lib/index.js index 1f294f0aa..18e6f5ffc 100644 --- a/packages/support/lib/index.js +++ b/packages/support/lib/index.js @@ -1,10 +1,10 @@ import * as tempDir from './tempdir'; import * as system from './system'; import * as util from './util'; -import { fs } from './fs'; +import {fs} from './fs'; import * as net from './net'; import * as plist from './plist'; -import { mkdirp } from './mkdirp'; +import {mkdirp} from './mkdirp'; import * as logger from './logging'; import * as process from './process'; import * as zip from './zip'; @@ -14,15 +14,43 @@ import * as node from './node'; import * as timing from './timing'; import * as env from './env'; -export { npm } from './npm'; +export {npm} from './npm'; -const { cancellableDelay } = util; +const {cancellableDelay} = util; export { - tempDir, system, util, fs, cancellableDelay, plist, mkdirp, logger, process, - zip, imageUtil, net, mjpeg, node, timing, env + tempDir, + system, + util, + fs, + cancellableDelay, + plist, + mkdirp, + logger, + process, + zip, + imageUtil, + net, + mjpeg, + node, + timing, + env, }; export default { - tempDir, system, util, fs, cancellableDelay, plist, mkdirp, logger, process, - zip, imageUtil, net, mjpeg, node, timing, env + tempDir, + system, + util, + fs, + cancellableDelay, + plist, + mkdirp, + logger, + process, + zip, + imageUtil, + net, + mjpeg, + node, + timing, + env, }; diff --git a/packages/support/lib/log-internal.js b/packages/support/lib/log-internal.js index 601866f05..9c8dcc5fa 100644 --- a/packages/support/lib/log-internal.js +++ b/packages/support/lib/log-internal.js @@ -3,9 +3,8 @@ import _ from 'lodash'; const DEFAULT_REPLACER = '**SECURE**'; - class SecureValuesPreprocessor { - constructor () { + constructor() { this._rules = []; } @@ -13,7 +12,7 @@ class SecureValuesPreprocessor { * @returns {Array} The list of successfully * parsed preprocessing rules */ - get rules () { + get rules() { return this._rules; } @@ -25,7 +24,7 @@ class SecureValuesPreprocessor { * @throws {Error} If there was an error while parsing the rule * @returns {SecureValuePreprocessingRule} The parsed rule */ - parseRule (rule) { + parseRule(rule) { let pattern; let replacer = DEFAULT_REPLACER; let flags = ['g']; @@ -37,17 +36,23 @@ class SecureValuesPreprocessor { } else if (_.isPlainObject(rule)) { if (_.has(rule, 'pattern')) { if (!_.isString(rule.pattern) || rule.pattern.length === 0) { - throw new Error(`${JSON.stringify(rule)} -> The value of 'pattern' must be a valid non-empty string`); + throw new Error( + `${JSON.stringify(rule)} -> The value of 'pattern' must be a valid non-empty string` + ); } pattern = rule.pattern; } else if (_.has(rule, 'text')) { if (!_.isString(rule.text) || rule.text.length === 0) { - throw new Error(`${JSON.stringify(rule)} -> The value of 'text' must be a valid non-empty string`); + throw new Error( + `${JSON.stringify(rule)} -> The value of 'text' must be a valid non-empty string` + ); } pattern = `\\b${_.escapeRegExp(rule.text)}\\b`; } if (!pattern) { - throw new Error(`${JSON.stringify(rule)} -> Must either have a field named 'pattern' or 'text'`); + throw new Error( + `${JSON.stringify(rule)} -> Must either have a field named 'pattern' or 'text'` + ); } if (_.has(rule, 'flags')) { @@ -83,12 +88,12 @@ class SecureValuesPreprocessor { * @returns {Promise} The list of issues found while parsing each rule. * An empty list is returned if no rule parsing issues were found */ - async loadRules (source) { + async loadRules(source) { let rules; if (_.isArray(source)) { rules = source; } else { - if (!await fs.exists(source)) { + if (!(await fs.exists(source))) { throw new Error(`'${source}' does not exist or is not accessible`); } try { @@ -121,7 +126,7 @@ class SecureValuesPreprocessor { * @param {string} str The string to make replacements in * @returns {string} The string with replacements made */ - preprocess (str) { + preprocess(str) { if (this._rules.length === 0 || !_.isString(str)) { return str; } @@ -136,7 +141,7 @@ class SecureValuesPreprocessor { const SECURE_VALUES_PREPROCESSOR = new SecureValuesPreprocessor(); -export { SECURE_VALUES_PREPROCESSOR, SecureValuesPreprocessor }; +export {SECURE_VALUES_PREPROCESSOR, SecureValuesPreprocessor}; export default SECURE_VALUES_PREPROCESSOR; /** diff --git a/packages/support/lib/logger.js b/packages/support/lib/logger.js index 13522ffc7..72f1c7c64 100644 --- a/packages/support/lib/logger.js +++ b/packages/support/lib/logger.js @@ -1,4 +1,4 @@ -import { getLogger } from './logging'; +import {getLogger} from './logging'; let log = getLogger('Support'); diff --git a/packages/support/lib/logging.js b/packages/support/lib/logging.js index 3900bd1b7..5e00fcd45 100644 --- a/packages/support/lib/logging.js +++ b/packages/support/lib/logging.js @@ -1,6 +1,6 @@ import npmlog from 'npmlog'; import _ from 'lodash'; -import { unleakString } from './util'; +import {unleakString} from './util'; import moment from 'moment'; import SECURE_VALUES_PREPROCESSOR from './log-internal'; @@ -16,9 +16,9 @@ for (let level of NPM_LEVELS) { mockLog[level] = () => {}; } -function patchLogger (logger) { +function patchLogger(logger) { if (!logger.debug) { - logger.addLevel('debug', 1000, { fg: 'blue', bg: 'black' }, 'dbug'); + logger.addLevel('debug', 1000, {fg: 'blue', bg: 'black'}, 'dbug'); } } @@ -26,7 +26,7 @@ function patchLogger (logger) { * * @returns {[npmlog.Logger, boolean]} */ -function _getLogger () { +function _getLogger() { // check if the user set the `_TESTING` or `_FORCE_LOGS` flag const testingMode = process.env._TESTING === '1'; const forceLogMode = process.env._FORCE_LOGS === '1'; @@ -53,11 +53,9 @@ function _getLogger () { * @param {boolean} logTimestamp whether to include timestamps into log prefixes * @returns {string} */ -function getActualPrefix (prefix, logTimestamp = false) { +function getActualPrefix(prefix, logTimestamp = false) { const result = (_.isFunction(prefix) ? prefix() : prefix) ?? ''; - return logTimestamp - ? `[${moment().format(PREFIX_TIMESTAMP_FORMAT)}] ${result}` - : result; + return logTimestamp ? `[${moment().format(PREFIX_TIMESTAMP_FORMAT)}] ${result}` : result; } /** @@ -65,7 +63,7 @@ function getActualPrefix (prefix, logTimestamp = false) { * @param {Prefix?} prefix * @returns {AppiumLogger} */ -function getLogger (prefix = null) { +function getLogger(prefix = null) { let [logger, usingGlobalLog] = _getLogger(); // wrap the logger so that we can catch and modify any logging @@ -77,14 +75,14 @@ function getLogger (prefix = null) { // allow access to the level of the underlying logger Object.defineProperty(wrappedLogger, 'level', { - get () { + get() { return logger.level; }, - set (newValue) { + set(newValue) { logger.level = newValue; }, enumerable: true, - configurable: true + configurable: true, }); const logTimestamp = process.env._LOG_TIMESTAMP === '1'; @@ -94,7 +92,7 @@ function getLogger (prefix = null) { wrappedLogger[level] = function (...args) { const actualPrefix = getActualPrefix(this.prefix, logTimestamp); for (const arg of args) { - const out = (_.isError(arg) && arg.stack) ? arg.stack : `${arg}`; + const out = _.isError(arg) && arg.stack ? arg.stack : `${arg}`; for (const line of out.split('\n')) { // it is necessary to unleak each line because `split` call // creates "views" to the original string as well as the `substring` one @@ -108,7 +106,7 @@ function getLogger (prefix = null) { wrappedLogger.errorAndThrow = function (err) { this.error(err); // make sure we have an `Error` object. Wrap if necessary - throw (_.isError(err) ? err : new Error(unleakString(err))); + throw _.isError(err) ? err : new Error(unleakString(err)); }; if (!usingGlobalLog) { // if we're not using a global log specified from some top-level package, @@ -116,7 +114,7 @@ function getLogger (prefix = null) { // package set the log level wrappedLogger.level = 'verbose'; } - return /** @type {AppiumLogger} */(wrappedLogger); + return /** @type {AppiumLogger} */ (wrappedLogger); } /** @@ -140,7 +138,7 @@ function getLogger (prefix = null) { * @throws {Error} If the given file cannot be loaded * @returns {Promise} */ -async function loadSecureValuesPreprocessingRules (rulesJsonPath) { +async function loadSecureValuesPreprocessingRules(rulesJsonPath) { const issues = await SECURE_VALUES_PREPROCESSOR.loadRules(rulesJsonPath); return { issues, @@ -151,7 +149,7 @@ async function loadSecureValuesPreprocessingRules (rulesJsonPath) { // export a default logger with no prefix const log = getLogger(); -export { log, patchLogger, getLogger, loadSecureValuesPreprocessingRules }; +export {log, patchLogger, getLogger, loadSecureValuesPreprocessingRules}; export default log; /** diff --git a/packages/support/lib/mjpeg.js b/packages/support/lib/mjpeg.js index a152ae89b..5f7b7aa79 100644 --- a/packages/support/lib/mjpeg.js +++ b/packages/support/lib/mjpeg.js @@ -1,9 +1,9 @@ import _ from 'lodash'; import log from './logger'; import B from 'bluebird'; -import { getJimpImage, MIME_PNG } from './image-util'; -import { Writable } from 'stream'; -import { requirePackage } from './node'; +import {getJimpImage, MIME_PNG} from './image-util'; +import {Writable} from 'stream'; +import {requirePackage} from './node'; import axios from 'axios'; // lazy load this, as it might not be available @@ -12,15 +12,17 @@ let MJpegConsumer = null; /** * @throws {Error} If `mjpeg-consumer` module is not installed or cannot be loaded */ -async function initMJpegConsumer () { +async function initMJpegConsumer() { if (!MJpegConsumer) { try { MJpegConsumer = await requirePackage('mjpeg-consumer'); } catch (ign) {} } if (!MJpegConsumer) { - throw new Error('mjpeg-consumer module is required to use MJPEG-over-HTTP features. ' + - 'Please install it first (npm i -g mjpeg-consumer) and restart Appium.'); + throw new Error( + 'mjpeg-consumer module is required to use MJPEG-over-HTTP features. ' + + 'Please install it first (npm i -g mjpeg-consumer) and restart Appium.' + ); } } @@ -29,7 +31,6 @@ const MJPEG_SERVER_TIMEOUT_MS = 10000; /** Class which stores the last bit of data streamed into it */ class MJpegStream extends Writable { - /** * @type {number} */ @@ -42,7 +43,7 @@ class MJpegStream extends Writable { * called in the case of any errors. * @param {object} [options={}] - Options to pass to the Writable constructor */ - constructor (mJpegUrl, errorHandler = _.noop, options = {}) { + constructor(mJpegUrl, errorHandler = _.noop, options = {}) { super(options); this.errorHandler = errorHandler; @@ -56,8 +57,8 @@ class MJpegStream extends Writable { * @returns {?string} base64-encoded JPEG image data * or `null` if no image can be parsed */ - get lastChunkBase64 () { - const lastChunk = /** @type {Buffer} */(this.lastChunk); + get lastChunkBase64() { + const lastChunk = /** @type {Buffer} */ (this.lastChunk); return !_.isEmpty(this.lastChunk) && _.isBuffer(this.lastChunk) ? lastChunk.toString('base64') : null; @@ -69,8 +70,8 @@ class MJpegStream extends Writable { * @returns {Promise} PNG image data or `null` if no PNG * image can be parsed */ - async lastChunkPNG () { - const lastChunk = /** @type {Buffer} */(this.lastChunk); + async lastChunkPNG() { + const lastChunk = /** @type {Buffer} */ (this.lastChunk); if (_.isEmpty(lastChunk) || !_.isBuffer(lastChunk)) { return null; } @@ -89,7 +90,7 @@ class MJpegStream extends Writable { * @returns {Promise} base64-encoded PNG image data * or `null` if no image can be parsed */ - async lastChunkPNGBase64 () { + async lastChunkPNGBase64() { const png = await this.lastChunkPNG(); return png ? png.toString('base64') : null; } @@ -97,7 +98,7 @@ class MJpegStream extends Writable { /** * Reset internal state */ - clear () { + clear() { this.registerStartSuccess = null; this.registerStartFailure = null; this.responseStream = null; @@ -109,7 +110,7 @@ class MJpegStream extends Writable { /** * Start reading the MJpeg stream and storing the last image */ - async start (serverTimeout = MJPEG_SERVER_TIMEOUT_MS) { + async start(serverTimeout = MJPEG_SERVER_TIMEOUT_MS) { // ensure we're not started already this.stop(); @@ -123,10 +124,12 @@ class MJpegStream extends Writable { this.registerStartSuccess = res; this.registerStartFailure = rej; }) - // start a timeout so that if the server does not return data, we don't - // block forever. - .timeout(serverTimeout, - `Waited ${serverTimeout}ms but the MJPEG server never sent any images`); + // start a timeout so that if the server does not return data, we don't + // block forever. + .timeout( + serverTimeout, + `Waited ${serverTimeout}ms but the MJPEG server never sent any images` + ); const url = this.url; const onErr = (err) => { @@ -145,11 +148,13 @@ class MJpegStream extends Writable { }; try { - this.responseStream = (await axios({ - url, - responseType: 'stream', - timeout: serverTimeout, - })).data; + this.responseStream = ( + await axios({ + url, + responseType: 'stream', + timeout: serverTimeout, + }) + ).data; } catch (e) { return onErr(e); } @@ -167,7 +172,7 @@ class MJpegStream extends Writable { * Stop reading the MJpeg stream. Ensure we disconnect all the pipes and stop * the HTTP request itself. Then reset the state. */ - stop () { + stop() { if (!this.consumer) { return; } @@ -184,7 +189,7 @@ class MJpegStream extends Writable { * @override * @param {Buffer} data - binary data streamed from the MJpeg consumer */ - write (data) { + write(data) { this.lastChunk = data; this.updateCount++; @@ -197,4 +202,4 @@ class MJpegStream extends Writable { } } -export { MJpegStream }; +export {MJpegStream}; diff --git a/packages/support/lib/mkdirp.js b/packages/support/lib/mkdirp.js index f0d3f869b..d3e03a1ae 100644 --- a/packages/support/lib/mkdirp.js +++ b/packages/support/lib/mkdirp.js @@ -1,6 +1,6 @@ -import { fs } from './fs'; +import {fs} from './fs'; /** * @deprecated Use `fs.mkdirp` instead. */ -const { mkdirp } = fs; -export { mkdirp }; +const {mkdirp} = fs; +export {mkdirp}; diff --git a/packages/support/lib/net.js b/packages/support/lib/net.js index 11bd7ad87..2a29775c6 100644 --- a/packages/support/lib/net.js +++ b/packages/support/lib/net.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import fs from './fs'; import B from 'bluebird'; -import { toReadableSizeString } from './util'; +import {toReadableSizeString} from './util'; import log from './logger'; import Ftp from 'jsftp'; import Timer from './timing'; @@ -15,7 +15,7 @@ const DEFAULT_TIMEOUT_MS = 4 * 60 * 1000; * @param {AuthCredentials | import('axios').AxiosBasicCredentials} auth * @returns {import('axios').AxiosBasicCredentials?} */ -function toAxiosAuth (auth) { +function toAxiosAuth(auth) { if (!_.isPlainObject(auth)) { return null; } @@ -24,7 +24,7 @@ function toAxiosAuth (auth) { username: _.get(auth, 'username', _.get(auth, 'user')), password: _.get(auth, 'password', _.get(auth, 'pass')), }; - return (axiosAuth.username && axiosAuth.password) ? axiosAuth : null; + return axiosAuth.username && axiosAuth.password ? axiosAuth : null; } /** @@ -32,7 +32,11 @@ function toAxiosAuth (auth) { * @param {URL} parsedUri * @param {HttpUploadOptions & NetOptions} [uploadOptions] */ -async function uploadFileToHttp (localFileStream, parsedUri, uploadOptions = /** @type {HttpUploadOptions & NetOptions} */({})) { +async function uploadFileToHttp( + localFileStream, + parsedUri, + uploadOptions = /** @type {HttpUploadOptions & NetOptions} */ ({}) +) { const { method = 'POST', timeout = DEFAULT_TIMEOUT_MS, @@ -41,7 +45,7 @@ async function uploadFileToHttp (localFileStream, parsedUri, uploadOptions = /** fileFieldName = 'file', formFields, } = uploadOptions; - const { href } = parsedUri; + const {href} = parsedUri; /** @type {import('axios').AxiosRequestConfig} */ const requestOpts = { @@ -73,7 +77,7 @@ async function uploadFileToHttp (localFileStream, parsedUri, uploadOptions = /** } requestOpts.headers = { ...(_.isPlainObject(headers) ? headers : {}), - ...form.getHeaders() + ...form.getHeaders(), }; requestOpts.data = form; } else { @@ -82,8 +86,10 @@ async function uploadFileToHttp (localFileStream, parsedUri, uploadOptions = /** } requestOpts.data = localFileStream; } - log.debug(`Performing ${method} to ${href} with options (excluding data): ` + - JSON.stringify(_.omit(requestOpts, ['data']))); + log.debug( + `Performing ${method} to ${href} with options (excluding data): ` + + JSON.stringify(_.omit(requestOpts, ['data'])) + ); const {status, statusText} = await axios(requestOpts); log.info(`Server response: ${status} ${statusText}`); @@ -94,16 +100,13 @@ async function uploadFileToHttp (localFileStream, parsedUri, uploadOptions = /** * @param {URL} parsedUri * @param {NotHttpUploadOptions & NetOptions} [uploadOptions] */ -async function uploadFileToFtp (localFileStream, parsedUri, uploadOptions = /** @type {NotHttpUploadOptions & NetOptions} */({})) { - const { - auth, - } = uploadOptions; - const { - hostname, - port, - protocol, - pathname, - } = parsedUri; +async function uploadFileToFtp( + localFileStream, + parsedUri, + uploadOptions = /** @type {NotHttpUploadOptions & NetOptions} */ ({}) +) { + const {auth} = uploadOptions; + const {hostname, port, protocol, pathname} = parsedUri; const ftpOpts = { host: hostname, @@ -131,7 +134,7 @@ async function uploadFileToFtp (localFileStream, parsedUri, uploadOptions = /** * @param {URL} url * @returns {opts is HttpUploadOptions & NetOptions} */ -function isHttpUploadOptions (opts, url) { +function isHttpUploadOptions(opts, url) { try { const {protocol} = new URL(url); return protocol === 'http:' || protocol === 'https:'; @@ -146,7 +149,7 @@ function isHttpUploadOptions (opts, url) { * @param {URL} url * @returns {opts is NotHttpUploadOptions & NetOptions} */ -function isNotHttpUploadOptions (opts, url) { +function isNotHttpUploadOptions(opts, url) { try { const {protocol} = new URL(url); return protocol === 'ftp:'; @@ -163,14 +166,16 @@ function isNotHttpUploadOptions (opts, url) { * @param {(HttpUploadOptions|NotHttpUploadOptions) & NetOptions} [uploadOptions] * @returns {Promise} */ -async function uploadFile (localPath, remoteUri, uploadOptions = /** @type {(HttpUploadOptions|NotHttpUploadOptions) & NetOptions} */({})) { - if (!await fs.exists(localPath)) { - throw new Error (`'${localPath}' does not exists or is not accessible`); +async function uploadFile( + localPath, + remoteUri, + uploadOptions = /** @type {(HttpUploadOptions|NotHttpUploadOptions) & NetOptions} */ ({}) +) { + if (!(await fs.exists(localPath))) { + throw new Error(`'${localPath}' does not exists or is not accessible`); } - const { - isMetered = true, - } = uploadOptions; + const {isMetered = true} = uploadOptions; const url = new URL(remoteUri); const {size} = await fs.stat(localPath); if (isMetered) { @@ -181,20 +186,24 @@ async function uploadFile (localPath, remoteUri, uploadOptions = /** @type {(Htt if (!uploadOptions.fileFieldName) { uploadOptions.headers = { ...(_.isPlainObject(uploadOptions.headers) ? uploadOptions.headers : {}), - 'Content-Length': size + 'Content-Length': size, }; } await uploadFileToHttp(fs.createReadStream(localPath), url, uploadOptions); } else if (isNotHttpUploadOptions(uploadOptions, url)) { await uploadFileToFtp(fs.createReadStream(localPath), url, uploadOptions); } else { - throw new Error(`Cannot upload the file at '${localPath}' to '${remoteUri}'. ` + - `Unsupported remote protocol '${url.protocol}'. ` + - `Only http/https and ftp/ftps protocols are supported.`); + throw new Error( + `Cannot upload the file at '${localPath}' to '${remoteUri}'. ` + + `Unsupported remote protocol '${url.protocol}'. ` + + `Only http/https and ftp/ftps protocols are supported.` + ); } if (isMetered) { - log.info(`Uploaded '${localPath}' of ${toReadableSizeString(size)} size in ` + - `${timer.getDuration().asSeconds.toFixed(3)}s`); + log.info( + `Uploaded '${localPath}' of ${toReadableSizeString(size)} size in ` + + `${timer.getDuration().asSeconds.toFixed(3)}s` + ); } } @@ -206,13 +215,12 @@ async function uploadFile (localPath, remoteUri, uploadOptions = /** @type {(Htt * @param {DownloadOptions & NetOptions} [downloadOptions] * @throws {Error} If download operation fails */ -async function downloadFile (remoteUrl, dstPath, downloadOptions = /** @type {DownloadOptions & NetOptions} */({})) { - const { - isMetered = true, - auth, - timeout = DEFAULT_TIMEOUT_MS, - headers, - } = downloadOptions; +async function downloadFile( + remoteUrl, + dstPath, + downloadOptions = /** @type {DownloadOptions & NetOptions} */ ({}) +) { + const {isMetered = true, auth, timeout = DEFAULT_TIMEOUT_MS, headers} = downloadOptions; /** * @type {import('axios').AxiosRequestConfig} @@ -234,10 +242,7 @@ async function downloadFile (remoteUrl, dstPath, downloadOptions = /** @type {Do let responseLength; try { const writer = fs.createWriteStream(dstPath); - const { - data: responseStream, - headers: responseHeaders, - } = await axios(requestOpts); + const {data: responseStream, headers: responseHeaders} = await axios(requestOpts); responseLength = parseInt(responseHeaders['content-length'], 10); responseStream.pipe(writer); @@ -256,13 +261,17 @@ async function downloadFile (remoteUrl, dstPath, downloadOptions = /** @type {Do const {size} = await fs.stat(dstPath); if (responseLength && size !== responseLength) { await fs.rimraf(dstPath); - throw new Error(`The size of the file downloaded from ${remoteUrl} (${size} bytes) ` + - `differs from the one in Content-Length response header (${responseLength} bytes)`); + throw new Error( + `The size of the file downloaded from ${remoteUrl} (${size} bytes) ` + + `differs from the one in Content-Length response header (${responseLength} bytes)` + ); } if (isMetered) { const secondsElapsed = timer.getDuration().asSeconds; - log.debug(`${remoteUrl} (${toReadableSizeString(size)}) ` + - `has been downloaded to '${dstPath}' in ${secondsElapsed.toFixed(3)}s`); + log.debug( + `${remoteUrl} (${toReadableSizeString(size)}) ` + + `has been downloaded to '${dstPath}' in ${secondsElapsed.toFixed(3)}s` + ); if (secondsElapsed >= 2) { const bytesPerSec = Math.floor(size / secondsElapsed); log.debug(`Approximate download speed: ${toReadableSizeString(bytesPerSec)}/s`); @@ -270,7 +279,7 @@ async function downloadFile (remoteUrl, dstPath, downloadOptions = /** @type {Do } } -export { uploadFile, downloadFile }; +export {uploadFile, downloadFile}; /** * Common options for {@linkcode uploadFile} and {@linkcode downloadFile}. @@ -316,4 +325,3 @@ export { uploadFile, downloadFile }; * to be included into the upload request. This property is only considered if * `fileFieldName` is set */ - diff --git a/packages/support/lib/node.js b/packages/support/lib/node.js index 00582f2c1..6280b9b41 100644 --- a/packages/support/lib/node.js +++ b/packages/support/lib/node.js @@ -1,9 +1,9 @@ -import { isWindows } from './system'; +import {isWindows} from './system'; import log from './logger'; import _ from 'lodash'; -import { exec } from 'teen_process'; +import {exec} from 'teen_process'; import path from 'path'; -import { v4 as uuidV4 } from 'uuid'; +import {v4 as uuidV4} from 'uuid'; const ECMA_SIZES = Object.freeze({ STRING: 2, @@ -17,7 +17,7 @@ const ECMA_SIZES = Object.freeze({ * @param {string} packageName - name of the package to link * @throws {Error} If the command fails */ -async function linkGlobalPackage (packageName) { +async function linkGlobalPackage(packageName) { try { log.debug(`Linking package '${packageName}'`); const cmd = isWindows() ? 'npm.cmd' : 'npm'; @@ -43,7 +43,7 @@ async function linkGlobalPackage (packageName) { * @returns {Promise} - the package object * @throws {Error} If the package is not found locally or globally */ -async function requirePackage (packageName) { +async function requirePackage(packageName) { // first, get it in the normal way (see https://nodejs.org/api/modules.html#modules_all_together) try { log.debug(`Loading local package '${packageName}'`); @@ -54,7 +54,12 @@ async function requirePackage (packageName) { // second, get it from where it ought to be in the global node_modules try { - const globalPackageName = path.resolve(process.env.npm_config_prefix ?? '', 'lib', 'node_modules', packageName); + const globalPackageName = path.resolve( + process.env.npm_config_prefix ?? '', + 'lib', + 'node_modules', + packageName + ); log.debug(`Loading global package '${globalPackageName}'`); return require(globalPackageName); } catch (err) { @@ -71,18 +76,18 @@ async function requirePackage (packageName) { } } -function extractAllProperties (obj) { +function extractAllProperties(obj) { const stringProperties = []; for (const prop in obj) { stringProperties.push(prop); } if (_.isFunction(Object.getOwnPropertySymbols)) { - stringProperties.push(...(Object.getOwnPropertySymbols(obj))); + stringProperties.push(...Object.getOwnPropertySymbols(obj)); } return stringProperties; } -function _getSizeOfObject (seen, object) { +function _getSizeOfObject(seen, object) { if (_.isNil(object)) { return 0; } @@ -113,13 +118,13 @@ function _getSizeOfObject (seen, object) { return bytes; } -function getCalculator (seen) { - return function calculator (obj) { +function getCalculator(seen) { + return function calculator(obj) { if (_.isBuffer(obj)) { return obj.length; } - switch (typeof (obj)) { + switch (typeof obj) { case 'string': return obj.length * ECMA_SIZES.STRING; case 'boolean': @@ -128,7 +133,7 @@ function getCalculator (seen) { return ECMA_SIZES.NUMBER; case 'symbol': return _.isFunction(Symbol.keyFor) && Symbol.keyFor(obj) - ? /** @type {string} */(Symbol.keyFor(obj)).length * ECMA_SIZES.STRING + ? /** @type {string} */ (Symbol.keyFor(obj)).length * ECMA_SIZES.STRING : (obj.toString().length - 8) * ECMA_SIZES.STRING; case 'object': return _.isArray(obj) @@ -147,7 +152,7 @@ function getCalculator (seen) { * @param {*} obj An object whose size should be calculated * @returns {number} Object size in bytes. */ -function getObjectSize (obj) { +function getObjectSize(obj) { return getCalculator(new WeakSet())(obj); } @@ -159,7 +164,7 @@ const OBJECTS_MAPPING = new WeakMap(); * @param {object} object Any valid ECMA object * @returns {string} A uuidV4 string that uniquely identifies given object */ -function getObjectId (object) { +function getObjectId(object) { if (!OBJECTS_MAPPING.has(object)) { OBJECTS_MAPPING.set(object, uuidV4()); } @@ -179,7 +184,7 @@ function getObjectId (object) { * @returns {*} The same object that was passed to the * function after it was made immutable. */ -function deepFreeze (object) { +function deepFreeze(object) { let propNames; try { propNames = Object.getOwnPropertyNames(object); @@ -195,4 +200,4 @@ function deepFreeze (object) { return Object.freeze(object); } -export { requirePackage, getObjectSize, getObjectId, deepFreeze }; +export {requirePackage, getObjectSize, getObjectId, deepFreeze}; diff --git a/packages/support/lib/npm.js b/packages/support/lib/npm.js index 7ceb7e72e..0a9676a89 100644 --- a/packages/support/lib/npm.js +++ b/packages/support/lib/npm.js @@ -2,31 +2,23 @@ import path from 'path'; import semver from 'semver'; -import { hasAppiumDependency } from './env'; -import { exec } from 'teen_process'; -import { fs } from './fs'; +import {hasAppiumDependency} from './env'; +import {exec} from 'teen_process'; +import {fs} from './fs'; import * as util from './util'; import * as system from './system'; import resolveFrom from 'resolve-from'; - /** * Relative path to directory containing any Appium internal files * XXX: this is duplicated in `appium/lib/constants.js`. */ -export const CACHE_DIR_RELATIVE_PATH = path.join( - 'node_modules', - '.cache', - 'appium', -); +export const CACHE_DIR_RELATIVE_PATH = path.join('node_modules', '.cache', 'appium'); /** * Relative path to lockfile used when installing an extension via `appium` */ -export const INSTALL_LOCKFILE_RELATIVE_PATH = path.join( - CACHE_DIR_RELATIVE_PATH, - '.install.lock', -); +export const INSTALL_LOCKFILE_RELATIVE_PATH = path.join(CACHE_DIR_RELATIVE_PATH, '.install.lock'); /** * XXX: This should probably be a singleton, but it isn't. Maybe this module should just export functions? @@ -37,7 +29,7 @@ export class NPM { * @private * @param {string} cwd */ - _getInstallLockfilePath (cwd) { + _getInstallLockfilePath(cwd) { return path.join(cwd, INSTALL_LOCKFILE_RELATIVE_PATH); } @@ -51,8 +43,8 @@ export class NPM { * @param {ExecOpts} opts * @param {ExecOpts} [execOpts] */ - async exec (cmd, args, opts, execOpts = /** @type {ExecOpts} */({})) { - let { cwd, json, lockFile } = opts; + async exec(cmd, args, opts, execOpts = /** @type {ExecOpts} */ ({})) { + let {cwd, json, lockFile} = opts; // make sure we perform the current operation in cwd execOpts = {...execOpts, cwd}; @@ -81,8 +73,12 @@ export class NPM { ret.json = JSON.parse(stdout); } catch (ign) {} } catch (e) { - const {stdout = '', stderr = '', code = null} = /** @type {TeenProcessExecError} */(e); - const err = new Error(`npm command '${args.join(' ')}' failed with code ${code}.\n\nSTDOUT:\n${stdout.trim()}\n\nSTDERR:\n${stderr.trim()}`); + const {stdout = '', stderr = '', code = null} = /** @type {TeenProcessExecError} */ (e); + const err = new Error( + `npm command '${args.join( + ' ' + )}' failed with code ${code}.\n\nSTDOUT:\n${stdout.trim()}\n\nSTDERR:\n${stderr.trim()}` + ); throw err; } return ret; @@ -92,11 +88,13 @@ export class NPM { * @param {string} cwd * @param {string} pkg */ - async getLatestVersion (cwd, pkg) { - return (await this.exec('view', [pkg, 'dist-tags'], { - json: true, - cwd - })).json?.latest; + async getLatestVersion(cwd, pkg) { + return ( + await this.exec('view', [pkg, 'dist-tags'], { + json: true, + cwd, + }) + ).json?.latest; } /** @@ -104,11 +102,13 @@ export class NPM { * @param {string} pkg * @param {string} curVersion */ - async getLatestSafeUpgradeVersion (cwd, pkg, curVersion) { - const allVersions = (await this.exec('view', [pkg, 'versions'], { - json: true, - cwd - })).json; + async getLatestSafeUpgradeVersion(cwd, pkg, curVersion) { + const allVersions = ( + await this.exec('view', [pkg, 'versions'], { + json: true, + cwd, + }) + ).json; return this.getLatestSafeUpgradeFromVersions(curVersion, allVersions); } @@ -117,7 +117,7 @@ export class NPM { * @param {string} cwd * @param {string} [pkg] */ - async list (cwd, pkg) { + async list(cwd, pkg) { return (await this.exec('list', pkg ? [pkg] : [], {cwd, json: true})).json; } @@ -131,7 +131,7 @@ export class NPM { * * @return {string|null} - the highest safely-upgradable version, or null if there isn't one */ - getLatestSafeUpgradeFromVersions (curVersion, allVersions) { + getLatestSafeUpgradeFromVersions(curVersion, allVersions) { let safeUpgradeVer = null; const curSemver = semver.parse(curVersion); if (curSemver === null) { @@ -173,7 +173,7 @@ export class NPM { * @param {InstallPackageOpts} [opts] * @returns {Promise} */ - async installPackage (cwd, pkgName, {pkgVer} = {}) { + async installPackage(cwd, pkgName, {pkgVer} = {}) { /** @type {any} */ let dummyPkgJson; const dummyPkgPath = path.join(cwd, 'package.json'); @@ -199,18 +199,19 @@ export class NPM { * "dummy" and is controlled by the user. So we'll just add it as a dev * dep; whatever else it does is up to the user's npm config. */ - const installOpts = await hasAppiumDependency(cwd) ? - ['--save-dev'] : - ['--save-dev', '--save-exact', '--global-style', '--no-package-lock']; + const installOpts = (await hasAppiumDependency(cwd)) + ? ['--save-dev'] + : ['--save-dev', '--save-exact', '--global-style', '--no-package-lock']; - const res = await this.exec('install', [ - ...installOpts, - pkgVer ? `${pkgName}@${pkgVer}` : pkgName - ], { - cwd, - json: true, - lockFile: this._getInstallLockfilePath(cwd) - }); + const res = await this.exec( + 'install', + [...installOpts, pkgVer ? `${pkgName}@${pkgVer}` : pkgName], + { + cwd, + json: true, + lockFile: this._getInstallLockfilePath(cwd), + } + ); if (res.json) { // we parsed a valid json response, so if we got an error here, return that @@ -228,9 +229,11 @@ export class NPM { try { return require(pkgJsonPath); } catch { - throw new Error('The package was not downloaded correctly; its package.json ' + - 'did not exist or was unreadable. We looked for it at ' + - pkgJsonPath); + throw new Error( + 'The package was not downloaded correctly; its package.json ' + + 'did not exist or was unreadable. We looked for it at ' + + pkgJsonPath + ); } } @@ -238,10 +241,10 @@ export class NPM { * @param {string} cwd * @param {string} pkg */ - async uninstallPackage (cwd, pkg) { + async uninstallPackage(cwd, pkg) { await this.exec('uninstall', [pkg], { cwd, - lockFile: this._getInstallLockfilePath(cwd) + lockFile: this._getInstallLockfilePath(cwd), }); } } diff --git a/packages/support/lib/plist.js b/packages/support/lib/plist.js index 6df28f82a..570eff8b1 100644 --- a/packages/support/lib/plist.js +++ b/packages/support/lib/plist.js @@ -33,9 +33,7 @@ async function parsePlistFile(plist, mustExist = true, quiet = true) { if (mustExist) { log.errorAndThrow(`Plist file doesn't exist: '${plist}'`); } else { - log.debug( - `Plist file '${plist}' does not exist. Returning an empty plist.` - ); + log.debug(`Plist file '${plist}' does not exist. Returning an empty plist.`); return {}; } } @@ -54,9 +52,7 @@ async function parsePlistFile(plist, mustExist = true, quiet = true) { obj = await parseXmlPlistFile(plist); type = 'xml'; } catch (err) { - log.errorAndThrow( - `Could not parse plist file '${plist}' as XML: ${err.message}` - ); + log.errorAndThrow(`Could not parse plist file '${plist}' as XML: ${err.message}`); } } @@ -121,8 +117,7 @@ function getXmlPlist(data) { } if ( _.isBuffer(data) && - PLIST_IDENTIFIER.BUFFER.compare(data, 0, PLIST_IDENTIFIER.BUFFER.length) === - 0 + PLIST_IDENTIFIER.BUFFER.compare(data, 0, PLIST_IDENTIFIER.BUFFER.length) === 0 ) { return data.toString(); } @@ -136,11 +131,7 @@ function getBinaryPlist(data) { if ( _.isBuffer(data) && - BPLIST_IDENTIFIER.BUFFER.compare( - data, - 0, - BPLIST_IDENTIFIER.BUFFER.length - ) === 0 + BPLIST_IDENTIFIER.BUFFER.compare(data, 0, BPLIST_IDENTIFIER.BUFFER.length) === 0 ) { return data; } diff --git a/packages/support/lib/process.js b/packages/support/lib/process.js index 1da5785a9..6cb3f39a7 100644 --- a/packages/support/lib/process.js +++ b/packages/support/lib/process.js @@ -1,5 +1,4 @@ -import { exec } from 'teen_process'; - +import {exec} from 'teen_process'; /* * Exit Status for pgrep and pkill (`man pkill`) @@ -9,11 +8,14 @@ import { exec } from 'teen_process'; * 3. Fatal error: out of memory etc. */ -async function getProcessIds (appName) { +async function getProcessIds(appName) { let pids; try { let {stdout} = await exec('pgrep', ['-x', appName]); - pids = stdout.trim().split('\n').map((pid) => parseInt(pid, 10)); + pids = stdout + .trim() + .split('\n') + .map((pid) => parseInt(pid, 10)); } catch (err) { if (parseInt(err.code, 10) !== 1) { throw new Error(`Error getting process ids for app '${appName}': ${err.message}`); @@ -23,7 +25,7 @@ async function getProcessIds (appName) { return pids; } -async function killProcess (appName, force = false) { +async function killProcess(appName, force = false) { let pids = await getProcessIds(appName); if (pids.length === 0) { // the process is not running @@ -41,4 +43,4 @@ async function killProcess (appName, force = false) { } } -export { getProcessIds, killProcess }; +export {getProcessIds, killProcess}; diff --git a/packages/support/lib/system.js b/packages/support/lib/system.js index 3c48f64a9..ac87fb425 100644 --- a/packages/support/lib/system.js +++ b/packages/support/lib/system.js @@ -1,26 +1,26 @@ -import { exec } from 'teen_process'; +import {exec} from 'teen_process'; import _ from 'lodash'; import os from 'os'; const VERSION_PATTERN = /^(\d+\.\d+)/m; -function isWindows () { +function isWindows() { return os.type() === 'Windows_NT'; } -function isMac () { +function isMac() { return os.type() === 'Darwin'; } -function isLinux () { +function isLinux() { return !isWindows() && !isMac(); } -function isOSWin64 () { +function isOSWin64() { return process.arch === 'x64' || _.has(process.env, 'PROCESSOR_ARCHITEW6432'); } -async function arch () { +async function arch() { if (isLinux() || isMac()) { let {stdout} = await exec('uname', ['-m']); return stdout.trim() === 'i686' ? '32' : '64'; @@ -30,7 +30,7 @@ async function arch () { } } -async function macOsxVersion () { +async function macOsxVersion() { let stdout; try { stdout = (await exec('sw_vers', ['-productVersion'])).stdout.trim(); @@ -45,4 +45,4 @@ async function macOsxVersion () { return versionMatch[1]; } -export { isWindows, isMac, isLinux, isOSWin64, arch, macOsxVersion }; +export {isWindows, isMac, isLinux, isOSWin64, arch, macOsxVersion}; diff --git a/packages/support/lib/tempdir.js b/packages/support/lib/tempdir.js index c8dd64726..eb09e1810 100644 --- a/packages/support/lib/tempdir.js +++ b/packages/support/lib/tempdir.js @@ -15,16 +15,20 @@ const RDWR_EXCL = cnst.O_CREAT | cnst.O_TRUNC | cnst.O_RDWR | cnst.O_EXCL; * * @returns {Promise} A path to the temporary directory */ -async function tempDir () { +async function tempDir() { const now = new Date(); - const filePath = nodePath.join(process.env.APPIUM_TMP_DIR || os.tmpdir(), + const filePath = nodePath.join( + process.env.APPIUM_TMP_DIR || os.tmpdir(), [ - now.getFullYear(), now.getMonth(), now.getDate(), + now.getFullYear(), + now.getMonth(), + now.getDate(), '-', process.pid, '-', (Math.random() * 0x100000000 + 1).toString(36), - ].join('')); + ].join('') + ); // creates a temp directory using the date and a random string await fs.mkdir(filePath); @@ -46,7 +50,7 @@ async function tempDir () { * @param {string} [defaultPrefix] * @returns {Promise} A path to the temporary directory with rawAffixes and defaultPrefix */ -async function path (rawAffixes, defaultPrefix) { +async function path(rawAffixes, defaultPrefix) { const affixes = parseAffixes(rawAffixes, defaultPrefix); const name = `${affixes.prefix || ''}${affixes.suffix || ''}`; const tempDirectory = await tempDir(); @@ -66,7 +70,7 @@ async function path (rawAffixes, defaultPrefix) { * @param {Affixes} affixes * @returns {Promise} */ -async function open (affixes) { +async function open(affixes) { const filePath = await path(affixes, 'f-'); try { let fd = await fs.open(filePath, RDWR_EXCL, 0o600); @@ -85,7 +89,7 @@ async function open (affixes) { * @param {string} [defaultPrefix] * @returns {Affixes} */ -function parseAffixes (rawAffixes, defaultPrefix) { +function parseAffixes(rawAffixes, defaultPrefix) { /** @type {Affixes} */ let affixes = {}; if (rawAffixes) { @@ -119,8 +123,9 @@ const openDir = tempDir; * * @returns {Promise} A temp directory path whcih is defined as static in the same process */ -async function staticDir () { // eslint-disable-line require-await +// eslint-disable-next-line require-await +async function staticDir() { return _static; } -export { open, path, openDir, staticDir }; +export {open, path, openDir, staticDir}; diff --git a/packages/support/lib/timing.js b/packages/support/lib/timing.js index 90d5af0bc..08b01531d 100644 --- a/packages/support/lib/timing.js +++ b/packages/support/lib/timing.js @@ -1,19 +1,17 @@ import _ from 'lodash'; - const NS_PER_S = 1e9; const NS_PER_MS = 1e6; - /** * Class representing a duration, encapsulating the number and units. */ class Duration { - constructor (nanos) { + constructor(nanos) { this._nanos = nanos; } - get nanos () { + get nanos() { return this._nanos; } @@ -22,7 +20,7 @@ class Duration { * * @returns {number} The duration as nanoseconds */ - get asNanoSeconds () { + get asNanoSeconds() { return this.nanos; } @@ -31,7 +29,7 @@ class Duration { * * @returns {number} The duration as milliseconds */ - get asMilliSeconds () { + get asMilliSeconds() { return this.nanos / NS_PER_MS; } @@ -40,11 +38,11 @@ class Duration { * * @returns {number} The duration fas seconds */ - get asSeconds () { + get asSeconds() { return this.nanos / NS_PER_S; } - toString () { + toString() { // default to milliseconds, rounded return this.asMilliSeconds.toFixed(0); } @@ -54,11 +52,11 @@ class Timer { /** * Creates a timer */ - constructor () { + constructor() { this._startTime = null; } - get startTime () { + get startTime() { return this._startTime; } @@ -67,7 +65,7 @@ class Timer { * * @return {Timer} The current instance, for chaining */ - start () { + start() { if (!_.isNull(this.startTime)) { throw new Error('Timer has already been started.'); } @@ -83,7 +81,7 @@ class Timer { * * @return {Duration} the duration */ - getDuration () { + getDuration() { if (_.isNull(this.startTime)) { throw new Error(`Unable to get duration. Timer was not started`); } @@ -105,7 +103,7 @@ class Timer { return new Duration(nanoDuration); } - toString () { + toString() { try { return this.getDuration().toString(); } catch (err) { @@ -114,6 +112,5 @@ class Timer { } } - -export { Timer, Duration }; +export {Timer, Duration}; export default Timer; diff --git a/packages/support/lib/util.js b/packages/support/lib/util.js index 79fb4a8e3..b718e7af9 100644 --- a/packages/support/lib/util.js +++ b/packages/support/lib/util.js @@ -13,26 +13,27 @@ import { } from 'shell-quote'; import pluralizeLib from 'pluralize'; import stream from 'stream'; -import { Base64Encode } from 'base64-stream'; +import {Base64Encode} from 'base64-stream'; import { // https://www.npmjs.com/package/uuid - v1 as uuidV1, v3 as uuidV3, - v4 as uuidV4, v5 as uuidV5 + v1 as uuidV1, + v3 as uuidV3, + v4 as uuidV4, + v5 as uuidV5, } from 'uuid'; import _lockfile from 'lockfile'; - const W3C_WEB_ELEMENT_IDENTIFIER = 'element-6066-11e4-a52e-4f735466cecf'; const KiB = 1024; const MiB = KiB * 1024; const GiB = MiB * 1024; -export function hasContent (val) { +export function hasContent(val) { return _.isString(val) && val !== ''; } // return true if the the value is not undefined, null, or NaN. -function hasValue (val) { +function hasValue(val) { let hasVal = false; // avoid incorrectly evaluating `0` as false if (_.isNumber(val)) { @@ -45,11 +46,11 @@ function hasValue (val) { } // escape spaces in string, for commandline calls -function escapeSpace (str) { +function escapeSpace(str) { return str.split(/ /).join('\\ '); } -function escapeSpecialChars (str, quoteEscape) { +function escapeSpecialChars(str, quoteEscape) { if (typeof str !== 'string') { return str; } @@ -73,13 +74,13 @@ function escapeSpecialChars (str, quoteEscape) { return str; } -function localIp () { +function localIp() { let ip = _.chain(os.networkInterfaces()) .values() .flatten() // @ts-ignore .filter(function (val) { - return (val.family === 'IPv4' && val.internal === false); + return val.family === 'IPv4' && val.internal === false; }) .map('address') .first() @@ -91,7 +92,7 @@ function localIp () { * Creates a promise that is cancellable, and will timeout * after `ms` delay */ -function cancellableDelay (ms) { +function cancellableDelay(ms) { let timer; let resolve; let reject; @@ -113,14 +114,14 @@ function cancellableDelay (ms) { return delay; } -function multiResolve (roots, ...args) { +function multiResolve(roots, ...args) { return roots.map((root) => path.resolve(root, ...args)); } /* * Parses an object if possible. Otherwise returns the object without parsing. */ -function safeJsonParse (obj) { +function safeJsonParse(obj) { try { return JSON.parse(obj); } catch (ign) { @@ -141,7 +142,7 @@ function safeJsonParse (obj) { * string for readability purposes. Defaults to 2 * returns {string} - the JSON object serialized as a string */ -function jsonStringify (obj, replacer, space = 2) { +function jsonStringify(obj, replacer, space = 2) { // if no replacer is passed, or it is not a function, just use a pass-through if (!_.isFunction(replacer)) { replacer = (k, v) => v; @@ -151,12 +152,14 @@ function jsonStringify (obj, replacer, space = 2) { const bufferToJSON = Buffer.prototype.toJSON; delete Buffer.prototype.toJSON; try { - return JSON.stringify(obj, (key, value) => { - const updatedValue = Buffer.isBuffer(value) - ? value.toString('utf8') - : value; - return replacer(key, updatedValue); - }, space); + return JSON.stringify( + obj, + (key, value) => { + const updatedValue = Buffer.isBuffer(value) ? value.toString('utf8') : value; + return replacer(key, updatedValue); + }, + space + ); } finally { // restore the function, so as to not break further serialization Buffer.prototype.toJSON = bufferToJSON; @@ -168,7 +171,7 @@ function jsonStringify (obj, replacer, space = 2) { * { ELEMENT: 4 } becomes 4 * { element-6066-11e4-a52e-4f735466cecf: 5 } becomes 5 */ -function unwrapElement (el) { +function unwrapElement(el) { for (const propName of [W3C_WEB_ELEMENT_IDENTIFIER, 'ELEMENT']) { if (_.has(el, propName)) { return el[propName]; @@ -177,7 +180,7 @@ function unwrapElement (el) { return el; } -function wrapElement (elementId) { +function wrapElement(elementId) { return { ELEMENT: elementId, [W3C_WEB_ELEMENT_IDENTIFIER]: elementId, @@ -192,7 +195,7 @@ function wrapElement (elementId) { * * a scalar - it will test all properties' values against that value * * a function - it will pass each value and the original object into the function */ -function filterObject (obj, predicate) { +function filterObject(obj, predicate) { let newObj = _.clone(obj); if (_.isUndefined(predicate)) { // remove any element from the object whose value is undefined @@ -219,7 +222,7 @@ function filterObject (obj, predicate) { * @throws {Error} If bytes count cannot be converted to an integer or * if it is less than zero. */ -function toReadableSizeString (bytes) { +function toReadableSizeString(bytes) { const intBytes = parseInt(String(bytes), 10); if (isNaN(intBytes) || intBytes < 0) { throw new Error(`Cannot convert '${bytes}' to a readable size format`); @@ -244,7 +247,7 @@ function toReadableSizeString (bytes) { * @returns {boolean} true if the given original path is the subpath of the root folder * @throws {Error} if any of the given paths is not absolute */ -function isSubPath (originalPath, root, forcePosix = null) { +function isSubPath(originalPath, root, forcePosix = null) { const pathObj = forcePosix ? path.posix : path; for (const p of [originalPath, root]) { if (!pathObj.isAbsolute(p)) { @@ -265,20 +268,23 @@ function isSubPath (originalPath, root, forcePosix = null) { * @param {...string} pathN - Zero or more absolute or relative paths to files/folders * @returns {Promise} true if all paths are pointing to the same file system item */ -async function isSameDestination (path1, path2, ...pathN) { +async function isSameDestination(path1, path2, ...pathN) { const allPaths = [path1, path2, ...pathN]; - if (!await B.reduce(allPaths, async (a, b) => a && await fs.exists(b), true)) { + if (!(await B.reduce(allPaths, async (a, b) => a && (await fs.exists(b)), true))) { return false; } - const areAllItemsEqual = (arr) => !!arr.reduce((a, b) => a === b ? a : NaN); + const areAllItemsEqual = (arr) => !!arr.reduce((a, b) => (a === b ? a : NaN)); if (areAllItemsEqual(allPaths)) { return true; } - let mapCb = async (x) => (await fs.stat(x, { - bigint: true, - })).ino; + let mapCb = async (x) => + ( + await fs.stat(x, { + bigint: true, + }) + ).ino; return areAllItemsEqual(await B.map(allPaths, mapCb)); } @@ -293,12 +299,12 @@ async function isSameDestination (path1, path2, ...pathN) { * coerced and strict mode is disabled * @throws {Error} if strict mode is enabled and `ver` cannot be coerced */ -function coerceVersion (ver, strict = /** @type {Strict} */(true)) { +function coerceVersion(ver, strict = /** @type {Strict} */ (true)) { const result = semver.valid(semver.coerce(`${ver}`)); if (strict && !result) { throw new Error(`'${ver}' cannot be coerced to a valid version number`); } - return /** @type {Strict extends true ? string : string?} */(result); + return /** @type {Strict extends true ? string : string?} */ (result); } const SUPPORTED_OPERATORS = ['==', '!=', '>', '<', '>=', '<=', '=']; @@ -316,10 +322,12 @@ const SUPPORTED_OPERATORS = ['==', '!=', '>', '<', '>=', '<=', '=']; * @throws {Error} if an unsupported operator is supplied or any of the supplied * version strings cannot be coerced */ -function compareVersions (ver1, operator, ver2) { +function compareVersions(ver1, operator, ver2) { if (!SUPPORTED_OPERATORS.includes(operator)) { - throw new Error(`The '${operator}' comparison operator is not supported. ` + - `Only '${JSON.stringify(SUPPORTED_OPERATORS)}' operators are supported`); + throw new Error( + `The '${operator}' comparison operator is not supported. ` + + `Only '${JSON.stringify(SUPPORTED_OPERATORS)}' operators are supported` + ); } const semverOperator = ['==', '!='].includes(operator) ? '=' : operator; @@ -334,7 +342,7 @@ function compareVersions (ver1, operator, ver2) { * @param {string|string[]} args - The arguments that will be parsed * @returns {string} - The arguments, quoted */ -function quote (args) { +function quote(args) { return shellQuote(_.castArray(args)); } @@ -346,11 +354,10 @@ function quote (args) { * @param {*} s - The string to unleak * @return {string} Either the unleaked string or the original object converted to string */ -function unleakString (s) { +function unleakString(s) { return ` ${s}`.substr(1); } - /** * @typedef PluralizeOptions * @property {boolean} [inclusive=false] - Whether to prefix with the number (e.g., 3 ducks) @@ -365,7 +372,7 @@ function unleakString (s) { * or a boolean indicating the options.inclusive property * @returns {string} The word pluralized according to the number */ -function pluralize (word, count, options = {}) { +function pluralize(word, count, options = {}) { let inclusive = false; if (_.isBoolean(options)) { // if passed in as a boolean @@ -397,14 +404,12 @@ function pluralize (word, count, options = {}) { * @throws {Error} if there was an error while reading the source file * or the source file is too */ -async function toInMemoryBase64 (srcPath, opts = {}) { +async function toInMemoryBase64(srcPath, opts = {}) { if (!(await fs.exists(srcPath)) || (await fs.stat(srcPath)).isDirectory()) { throw new Error(`No such file: ${srcPath}`); } - const { - maxSize = 1 * GiB, - } = opts; + const {maxSize = 1 * GiB} = opts; const resultBuffers = []; let resultBuffersSize = 0; const resultWriteStream = new stream.Writable({ @@ -412,8 +417,13 @@ async function toInMemoryBase64 (srcPath, opts = {}) { resultBuffers.push(buffer); resultBuffersSize += buffer.length; if (maxSize > 0 && resultBuffersSize > maxSize) { - resultWriteStream.emit('error', new Error(`The size of the resulting ` + - `buffer must not be greater than ${toReadableSizeString(maxSize)}`)); + resultWriteStream.emit( + 'error', + new Error( + `The size of the resulting ` + + `buffer must not be greater than ${toReadableSizeString(maxSize)}` + ) + ); } next(); }, @@ -432,8 +442,9 @@ async function toInMemoryBase64 (srcPath, opts = {}) { }); const readStreamPromise = new B((resolve, reject) => { readerStream.once('close', resolve); - readerStream.once('error', (e) => reject( - new Error(`Failed to read '${srcPath}': ${e.message}`))); + readerStream.once('error', (e) => + reject(new Error(`Failed to read '${srcPath}': ${e.message}`)) + ); }); readerStream.pipe(base64EncoderStream); base64EncoderStream.pipe(resultWriteStream); @@ -460,13 +471,12 @@ async function toInMemoryBase64 (srcPath, opts = {}) { * @returns async function that takes another async function defining the locked * behavior */ -function getLockFileGuard (lockFile, opts = {}) { - const { - timeout = 120, - tryRecovery = false, - } = opts; +function getLockFileGuard(lockFile, opts = {}) { + const {timeout = 120, tryRecovery = false} = opts; - const lock = /** @type {(lockfile: string, opts: import('lockfile').Options)=>B} */(B.promisify(_lockfile.lock)); + const lock = /** @type {(lockfile: string, opts: import('lockfile').Options)=>B} */ ( + B.promisify(_lockfile.lock) + ); const check = B.promisify(_lockfile.check); const unlock = B.promisify(_lockfile.unlock); @@ -495,10 +505,12 @@ function getLockFileGuard (lockFile, opts = {}) { triedRecovery = true; continue; } - throw new Error(`Could not acquire lock on '${lockFile}' after ${timeout}s. ` + - `Original error: ${e.message}`); + throw new Error( + `Could not acquire lock on '${lockFile}' after ${timeout}s. ` + + `Original error: ${e.message}` + ); } - // eslint-disable-next-line no-constant-condition + // eslint-disable-next-line no-constant-condition } while (true); try { return await behavior(); @@ -514,10 +526,34 @@ function getLockFileGuard (lockFile, opts = {}) { } export { - hasValue, escapeSpace, escapeSpecialChars, localIp, cancellableDelay, - multiResolve, safeJsonParse, wrapElement, unwrapElement, filterObject, - toReadableSizeString, isSubPath, W3C_WEB_ELEMENT_IDENTIFIER, - isSameDestination, compareVersions, coerceVersion, quote, unleakString, - jsonStringify, pluralize, GiB, MiB, KiB, toInMemoryBase64, - uuidV1, uuidV3, uuidV4, uuidV5, shellParse, getLockFileGuard + hasValue, + escapeSpace, + escapeSpecialChars, + localIp, + cancellableDelay, + multiResolve, + safeJsonParse, + wrapElement, + unwrapElement, + filterObject, + toReadableSizeString, + isSubPath, + W3C_WEB_ELEMENT_IDENTIFIER, + isSameDestination, + compareVersions, + coerceVersion, + quote, + unleakString, + jsonStringify, + pluralize, + GiB, + MiB, + KiB, + toInMemoryBase64, + uuidV1, + uuidV3, + uuidV4, + uuidV5, + shellParse, + getLockFileGuard, }; diff --git a/packages/support/lib/zip.js b/packages/support/lib/zip.js index c09ccb4a4..5421e5153 100644 --- a/packages/support/lib/zip.js +++ b/packages/support/lib/zip.js @@ -2,17 +2,17 @@ import _ from 'lodash'; import B from 'bluebird'; import yauzl from 'yauzl'; import archiver from 'archiver'; -import { createWriteStream } from 'fs'; +import {createWriteStream} from 'fs'; import path from 'path'; import stream from 'stream'; import fs from './fs'; -import { isWindows } from './system'; -import { Base64Encode } from 'base64-stream'; -import { toReadableSizeString, GiB } from './util'; +import {isWindows} from './system'; +import {Base64Encode} from 'base64-stream'; +import {toReadableSizeString, GiB} from './util'; import Timer from './timing'; import log from './logger'; import getStream from 'get-stream'; -import { exec } from 'teen_process'; +import {exec} from 'teen_process'; /** * @type {(path: string, options?: yauzl.Options) => Promise} @@ -32,21 +32,20 @@ class ZipExtractor { /** @type {yauzl.ZipFile} */ zipfile; - constructor (sourcePath, opts = {}) { + constructor(sourcePath, opts = {}) { this.zipPath = sourcePath; this.opts = opts; this.canceled = false; } - extractFileName (entry) { - return _.isBuffer(entry.fileName) ? entry.fileName.toString(this.opts.fileNamesEncoding) : entry.fileName; + extractFileName(entry) { + return _.isBuffer(entry.fileName) + ? entry.fileName.toString(this.opts.fileNamesEncoding) + : entry.fileName; } - async extract () { - const { - dir, - fileNamesEncoding, - } = this.opts; + async extract() { + const {dir, fileNamesEncoding} = this.opts; this.zipfile = await openZip(this.zipPath, { lazyEntries: true, // https://github.com/thejoshwolfe/yauzl/commit/cc7455ac789ba84973184e5ebde0581cdc4c3b39#diff-04c6e90faac2675aa89e2176d2eec7d8R95 @@ -86,7 +85,9 @@ class ZipExtractor { const relativeDestDir = path.relative(dir, canonicalDestDir); if (relativeDestDir.split(path.sep).includes('..')) { - new Error(`Out of bound path "${canonicalDestDir}" found while processing file ${fileName}`); + new Error( + `Out of bound path "${canonicalDestDir}" found while processing file ${fileName}` + ); } await this.extractEntry(entry); @@ -100,32 +101,31 @@ class ZipExtractor { }); } - async extractEntry (entry) { + async extractEntry(entry) { if (this.canceled) { return; } - const { - dir, - } = this.opts; + const {dir} = this.opts; const fileName = this.extractFileName(entry); const dest = path.join(dir, fileName); // convert external file attr int into a fs stat mode int - const mode = (entry.externalFileAttributes >> 16) & 0xFFFF; + const mode = (entry.externalFileAttributes >> 16) & 0xffff; // check if it's a symlink or dir (using stat mode constants) const isSymlink = (mode & IFMT) === IFLNK; - const isDir = (mode & IFMT) === IFDIR + const isDir = + (mode & IFMT) === IFDIR || // Failsafe, borrowed from jsZip - || fileName.endsWith('/') + fileName.endsWith('/') || // check for windows weird way of specifying a directory // https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566 - || (entry.versionMadeBy >> 8 === 0 && entry.externalFileAttributes === 16); + (entry.versionMadeBy >> 8 === 0 && entry.externalFileAttributes === 16); const procMode = this.getExtractedMode(mode, isDir) & 0o777; // always ensure folders are created const destDir = isDir ? dest : path.dirname(dest); - const mkdirOptions = { recursive: true }; + const mkdirOptions = {recursive: true}; if (isDir) { mkdirOptions.mode = procMode; } @@ -141,15 +141,12 @@ class ZipExtractor { const link = await getStream(readStream); await fs.symlink(link, dest); } else { - await pipeline(readStream, fs.createWriteStream(dest, { mode: procMode })); + await pipeline(readStream, fs.createWriteStream(dest, {mode: procMode})); } } - getExtractedMode (entryMode, isDir) { - const { - defaultDirMode, - defaultFileMode, - } = this.opts; + getExtractedMode(entryMode, isDir) { + const {defaultDirMode, defaultFileMode} = this.opts; let mode = entryMode; // Set defaults, if necessary @@ -177,7 +174,6 @@ class ZipExtractor { } } - /** * @typedef ExtractAllOptions * @property {?string} fileNamesEncoding The encoding to use for extracted file names. @@ -196,7 +192,7 @@ class ZipExtractor { * @param {string} destDir The full path to the destination folder * @param {ExtractAllOptions} [opts] */ -async function extractAllTo (zipFilePath, destDir, opts = /** @type {ExtractAllOptions} */({})) { +async function extractAllTo(zipFilePath, destDir, opts = /** @type {ExtractAllOptions} */ ({})) { if (!path.isAbsolute(destDir)) { throw new Error(`Target path '${destDir}' is expected to be absolute`); } @@ -227,13 +223,11 @@ async function extractAllTo (zipFilePath, destDir, opts = /** @type {ExtractAllO * @param {string} destDir The full path to the destination folder. * This folder is expected to already exist before extracting the archive. */ -async function extractWithSystemUnzip (zipFilePath, destDir) { +async function extractWithSystemUnzip(zipFilePath, destDir) { const isWindowsHost = isWindows(); let executablePath; try { - executablePath = await getExecutablePath( - isWindowsHost ? 'powershell.exe' : 'unzip' - ); + executablePath = await getExecutablePath(isWindowsHost ? 'powershell.exe' : 'unzip'); } catch (e) { throw new Error('Could not find system unzip'); } @@ -241,20 +235,19 @@ async function extractWithSystemUnzip (zipFilePath, destDir) { if (isWindowsHost) { // on Windows we use PowerShell to unzip files await exec(executablePath, [ - '-command', 'Expand-Archive', - '-LiteralPath', zipFilePath, - '-DestinationPath', destDir, - '-Force' + '-command', + 'Expand-Archive', + '-LiteralPath', + zipFilePath, + '-DestinationPath', + destDir, + '-Force', ]); } else { // -q means quiet (no stdout) // -o means overwrite // -d is the dest dir - await exec(executablePath, [ - '-q', - '-o', zipFilePath, - '-d', destDir - ]); + await exec(executablePath, ['-q', '-o', zipFilePath, '-d', destDir]); } } @@ -265,16 +258,16 @@ async function extractWithSystemUnzip (zipFilePath, destDir) { * @param {yauzl.Entry} entry The entry instance * @param {string} destDir The full path to the destination folder */ -async function _extractEntryTo (zipFile, entry, destDir) { +async function _extractEntryTo(zipFile, entry, destDir) { const dstPath = path.resolve(destDir, entry.fileName); // Create dest directory if doesn't exist already if (/\/$/.test(entry.fileName)) { - if (!await fs.exists(dstPath)) { + if (!(await fs.exists(dstPath))) { await fs.mkdirp(dstPath); } return; - } else if (!await fs.exists(path.dirname(dstPath))) { + } else if (!(await fs.exists(path.dirname(dstPath)))) { await fs.mkdirp(path.dirname(dstPath)); } @@ -288,7 +281,7 @@ async function _extractEntryTo (zipFile, entry, destDir) { // Create zipReadStream and pipe data to the write stream // (for some odd reason B.promisify doesn't work on zipfile.openReadStream, it causes an error 'closed') const zipReadStream = await new B((resolve, reject) => { - zipFile.openReadStream(entry, (err, readStream) => err ? reject(err) : resolve(readStream)); + zipFile.openReadStream(entry, (err, readStream) => (err ? reject(err) : resolve(readStream))); }); const zipReadStreamPromise = new B((resolve, reject) => { zipReadStream.once('end', resolve); @@ -297,10 +290,7 @@ async function _extractEntryTo (zipFile, entry, destDir) { zipReadStream.pipe(writeStream); // Wait for the zipReadStream and writeStream to end before returning - return await B.all([ - zipReadStreamPromise, - writeStreamPromise, - ]); + return await B.all([zipReadStreamPromise, writeStreamPromise]); } /** @@ -319,7 +309,7 @@ async function _extractEntryTo (zipFile, entry, destDir) { * The iteration through the source zip file will bi terminated as soon as * the result of this function equals to `false`. */ -async function readEntries (zipFilePath, onEntry) { +async function readEntries(zipFilePath, onEntry) { // Open a zip file and start reading entries const zipfile = await openZip(zipFilePath, {lazyEntries: true}); const zipReadStreamPromise = new B((resolve, reject) => { @@ -330,7 +320,7 @@ async function readEntries (zipFilePath, onEntry) { zipfile.on('entry', async (entry) => { const res = await onEntry({ entry, - extractEntryTo: async (destDir) => await _extractEntryTo(zipfile, entry, destDir) + extractEntryTo: async (destDir) => await _extractEntryTo(zipfile, entry, destDir), }); if (res === false) { return zipfile.emit('end'); @@ -370,17 +360,12 @@ async function readEntries (zipFilePath, onEntry) { * @throws {Error} if there was an error while reading the source * or the source is too big */ -async function toInMemoryZip (srcPath, opts = /** @type {ZipOptions} */({})) { - if (!await fs.exists(srcPath)) { +async function toInMemoryZip(srcPath, opts = /** @type {ZipOptions} */ ({})) { + if (!(await fs.exists(srcPath))) { throw new Error(`No such file or folder: ${srcPath}`); } - const { - isMetered = true, - encodeToBase64 = false, - maxSize = 1 * GiB, - level = 9, - } = opts; + const {isMetered = true, encodeToBase64 = false, maxSize = 1 * GiB, level = 9} = opts; const resultBuffers = []; let resultBuffersSize = 0; // Create a writable stream that zip buffers will be streamed to @@ -389,8 +374,13 @@ async function toInMemoryZip (srcPath, opts = /** @type {ZipOptions} */({})) { resultBuffers.push(buffer); resultBuffersSize += buffer.length; if (maxSize > 0 && resultBuffersSize > maxSize) { - resultWriteStream.emit('error', new Error(`The size of the resulting ` + - `archive must not be greater than ${toReadableSizeString(maxSize)}`)); + resultWriteStream.emit( + 'error', + new Error( + `The size of the resulting ` + + `archive must not be greater than ${toReadableSizeString(maxSize)}` + ) + ); } next(); }, @@ -398,7 +388,7 @@ async function toInMemoryZip (srcPath, opts = /** @type {ZipOptions} */({})) { // Zip 'srcDir' and stream it to the above writable stream const archive = archiver('zip', { - zlib: {level} + zlib: {level}, }); let srcSize = null; const base64EncoderStream = encodeToBase64 ? new Base64Encode() : null; @@ -421,8 +411,7 @@ async function toInMemoryZip (srcPath, opts = /** @type {ZipOptions} */({})) { }); const archiveStreamPromise = new B((resolve, reject) => { archive.once('finish', resolve); - archive.once('error', (e) => reject( - new Error(`Failed to archive '${srcPath}': ${e.message}`))); + archive.once('error', (e) => reject(new Error(`Failed to archive '${srcPath}': ${e.message}`))); }); const timer = isMetered ? new Timer().start() : null; if ((await fs.stat(srcPath)).isDirectory()) { @@ -444,11 +433,13 @@ async function toInMemoryZip (srcPath, opts = /** @type {ZipOptions} */({})) { await B.all([archiveStreamPromise, resultWriteStreamPromise]); if (timer) { - log.debug(`Zipped ${encodeToBase64 ? 'and base64-encoded ' : ''}` + - `'${path.basename(srcPath)}' ` + - (srcSize ? `(${toReadableSizeString(srcSize)}) ` : '') + - `in ${timer.getDuration().asSeconds.toFixed(3)}s ` + - `(compression level: ${level})`); + log.debug( + `Zipped ${encodeToBase64 ? 'and base64-encoded ' : ''}` + + `'${path.basename(srcPath)}' ` + + (srcSize ? `(${toReadableSizeString(srcSize)}) ` : '') + + `in ${timer.getDuration().asSeconds.toFixed(3)}s ` + + `(compression level: ${level})` + ); } // Return the array of zip buffers concatenated into one buffer return Buffer.concat(resultBuffers); @@ -460,8 +451,8 @@ async function toInMemoryZip (srcPath, opts = /** @type {ZipOptions} */({})) { * @param {string} filePath - Full path to the file * @throws {Error} If the file does not exist or is not a valid ZIP archive */ -async function assertValidZip (filePath) { - if (!await fs.exists(filePath)) { +async function assertValidZip(filePath) { + if (!(await fs.exists(filePath))) { throw new Error(`The file at '${filePath}' does not exist`); } @@ -475,8 +466,10 @@ async function assertValidZip (filePath) { await fs.read(fd, buffer, 0, ZIP_MAGIC.length, 0); const signature = buffer.toString('ascii'); if (signature !== ZIP_MAGIC) { - throw new Error(`The file signature '${signature}' of '${filePath}' ` + - `is not equal to the expected ZIP archive signature '${ZIP_MAGIC}'`); + throw new Error( + `The file signature '${signature}' of '${filePath}' ` + + `is not equal to the expected ZIP archive signature '${ZIP_MAGIC}'` + ); } return true; } finally { @@ -506,16 +499,14 @@ async function assertValidZip (filePath) { * @param {ZipCompressionOptions} opts - Compression options * @throws {Error} If there was an error while creating the archive */ -async function toArchive (dstPath, src = /** @type {ZipSourceOptions} */({}), opts = /** @type {ZipCompressionOptions} */({})) { - const { - level = 9, - } = opts; - const { - pattern = '**/*', - cwd = path.dirname(dstPath), - ignore = [], - } = src; - const archive = archiver('zip', { zlib: { level }}); +async function toArchive( + dstPath, + src = /** @type {ZipSourceOptions} */ ({}), + opts = /** @type {ZipCompressionOptions} */ ({}) +) { + const {level = 9} = opts; + const {pattern = '**/*', cwd = path.dirname(dstPath), ignore = []} = src; + const archive = archiver('zip', {zlib: {level}}); const stream = fs.createWriteStream(dstPath); return await new B((resolve, reject) => { archive @@ -545,13 +536,18 @@ const getExecutablePath = _.memoize( /** * @returns {Promise} Full Path to the executable */ - async function getExecutablePath (binaryName) { + async function getExecutablePath(binaryName) { const fullPath = await fs.which(binaryName); log.debug(`Found '%s' at '%s'`, binaryName, fullPath); return fullPath; } ); -export { extractAllTo, readEntries, toInMemoryZip, _extractEntryTo, - assertValidZip, toArchive }; -export default { extractAllTo, readEntries, toInMemoryZip, assertValidZip, toArchive }; +export {extractAllTo, readEntries, toInMemoryZip, _extractEntryTo, assertValidZip, toArchive}; +export default { + extractAllTo, + readEntries, + toInMemoryZip, + assertValidZip, + toArchive, +}; diff --git a/packages/support/test/e2e/env.e2e.spec.js b/packages/support/test/e2e/env.e2e.spec.js index f96e7b895..dcee6ef02 100644 --- a/packages/support/test/e2e/env.e2e.spec.js +++ b/packages/support/test/e2e/env.e2e.spec.js @@ -1,7 +1,7 @@ // @ts-check import path from 'path'; -import { fs, tempDir } from '../../lib'; +import {fs, tempDir} from '../../lib'; import { DEFAULT_APPIUM_HOME, readPackageInDir, @@ -42,7 +42,6 @@ describe('environment', function () { describe('resolution of APPIUM_HOME', function () { describe('when `appium` is not a package nor can be resolved from the CWD', function () { describe('when `APPIUM_HOME` is not present in the environment', function () { - describe('when providing no `cwd` parameter', function () { /** * **IMPORTANT:** If no `cwd` is provided, {@linkcode resolveManifestPath} call {@linkcode resolveAppiumHome}. @@ -64,17 +63,13 @@ describe('environment', function () { }); it('should resolve to the default `APPIUM_HOME`', async function () { - await expect(resolveAppiumHome()).to.eventually.equal( - DEFAULT_APPIUM_HOME, - ); + await expect(resolveAppiumHome()).to.eventually.equal(DEFAULT_APPIUM_HOME); }); }); describe('when providing a `cwd` parameter', function () { it('should resolve to the default `APPIUM_HOME`', async function () { - await expect(resolveAppiumHome(cwd)).to.eventually.equal( - DEFAULT_APPIUM_HOME, - ); + await expect(resolveAppiumHome(cwd)).to.eventually.equal(DEFAULT_APPIUM_HOME); }); }); }); @@ -86,17 +81,13 @@ describe('environment', function () { describe('when providing no `cwd` parameter', function () { it('should resolve with `APPIUM_HOME` from env', async function () { - await expect(resolveAppiumHome()).to.eventually.equal( - process.env.APPIUM_HOME, - ); + await expect(resolveAppiumHome()).to.eventually.equal(process.env.APPIUM_HOME); }); }); describe('when providing an `cwd` parameter', function () { it('should resolve with `APPIUM_HOME` from env', async function () { - await expect(resolveAppiumHome('/root')).to.eventually.equal( - process.env.APPIUM_HOME, - ); + await expect(resolveAppiumHome('/root')).to.eventually.equal(process.env.APPIUM_HOME); }); }); }); @@ -104,9 +95,7 @@ describe('environment', function () { describe('when `appium` is not a dependency', function () { it('should resolve with `DEFAULT_APPIUM_HOME`', async function () { - await expect(resolveAppiumHome(cwd)).to.eventually.equal( - DEFAULT_APPIUM_HOME, - ); + await expect(resolveAppiumHome(cwd)).to.eventually.equal(DEFAULT_APPIUM_HOME); }); }); describe('when `appium` is a dependency', function () { @@ -123,11 +112,11 @@ describe('environment', function () { before(async function () { await fs.copyFile( path.join(__dirname, 'fixture', 'appium-v2-dependency.package.json'), - path.join(cwd, 'package.json'), + path.join(cwd, 'package.json') ); await fs.copyFile( path.join(__dirname, 'fixture', 'appium-v2-package'), - path.join(cwd, 'node_modules', 'appium'), + path.join(cwd, 'node_modules', 'appium') ); }); @@ -136,20 +125,18 @@ describe('environment', function () { }); it('should resolve with `cwd`', async function () { - await expect(resolveAppiumHome(cwd)).to.eventually.equal( - cwd, - ); + await expect(resolveAppiumHome(cwd)).to.eventually.equal(cwd); }); }); describe('when `appium` is an old version', function () { before(async function () { await fs.copyFile( path.join(__dirname, 'fixture', 'appium-v1-dependency.package.json'), - path.join(cwd, 'package.json'), + path.join(cwd, 'package.json') ); await fs.copyFile( path.join(__dirname, 'fixture', 'appium-v1-package'), - path.join(cwd, 'node_modules', 'appium'), + path.join(cwd, 'node_modules', 'appium') ); }); @@ -158,9 +145,7 @@ describe('environment', function () { }); it('should resolve with `DEFAULT_APPIUM_HOME`', async function () { - await expect(resolveAppiumHome(cwd)).to.eventually.equal( - DEFAULT_APPIUM_HOME - ); + await expect(resolveAppiumHome(cwd)).to.eventually.equal(DEFAULT_APPIUM_HOME); }); }); }); @@ -170,7 +155,7 @@ describe('environment', function () { before(async function () { await fs.copyFile( path.join(__dirname, 'fixture', 'appium-v2-dependency.package.json'), - path.join(cwd, 'package.json'), + path.join(cwd, 'package.json') ); }); @@ -186,7 +171,7 @@ describe('environment', function () { before(async function () { await fs.copyFile( path.join(__dirname, 'fixture', 'appium-v1-dependency.package.json'), - path.join(cwd, 'package.json'), + path.join(cwd, 'package.json') ); }); diff --git a/packages/support/test/e2e/image-util.e2e.spec.js b/packages/support/test/e2e/image-util.e2e.spec.js index 9fe965fa8..955043b00 100644 --- a/packages/support/test/e2e/image-util.e2e.spec.js +++ b/packages/support/test/e2e/image-util.e2e.spec.js @@ -1,21 +1,22 @@ import { - base64ToImage, imageToBase64, cropImage, - getJimpImage, MIME_PNG, + base64ToImage, + imageToBase64, + cropImage, + getJimpImage, + MIME_PNG, } from '../../lib/image-util'; import path from 'path'; import _ from 'lodash'; -import { fs } from '../../lib'; - +import {fs} from '../../lib'; const FIXTURES_ROOT = path.resolve(__dirname, 'fixture', 'images'); -async function getImage (name) { +async function getImage(name) { const imagePath = path.resolve(FIXTURES_ROOT, name); return await fs.readFile(imagePath, 'utf8'); } describe('image-util', function () { - describe('cropBase64Image', function () { let originalImage = null; @@ -29,7 +30,12 @@ describe('image-util', function () { }); it('should verify that an image is cropped correctly', async function () { - const croppedImage = await cropImage(originalImage, {left: 35, top: 107, width: 323, height: 485}); + const croppedImage = await cropImage(originalImage, { + left: 35, + top: 107, + width: 323, + height: 485, + }); // verify cropped image size, it should be less than original image according to crop region croppedImage.width.should.be.equal(323, 'unexpected width'); diff --git a/packages/support/test/e2e/mjpeg.e2e.spec.js b/packages/support/test/e2e/mjpeg.e2e.spec.js index e9cf9351f..e83ff5640 100644 --- a/packages/support/test/e2e/mjpeg.e2e.spec.js +++ b/packages/support/test/e2e/mjpeg.e2e.spec.js @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { mjpeg } from '../../lib'; +import {mjpeg} from '../../lib'; import B from 'bluebird'; import http from 'http'; import mJpegServer from 'mjpeg-server'; @@ -7,7 +7,8 @@ import getPort from 'get-port'; const {MJpegStream} = mjpeg; -const TEST_IMG_JPG = '/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAeAAD/4QOBaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0MCA3OS4xNjA0NTEsIDIwMTcvMDUvMDYtMDE6MDg6MjEgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NGY5ODc1OTctZGE2My00Y2M0LTkzNDMtNGYyNjgzMGUwNjk3IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjlDMzI3QkY0N0Q3NTExRThCMTlDOTVDMDc2RDE5MDY5IiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjlDMzI3QkYzN0Q3NTExRThCMTlDOTVDMDc2RDE5MDY5IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NGY5ODc1OTctZGE2My00Y2M0LTkzNDMtNGYyNjgzMGUwNjk3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRmOTg3NTk3LWRhNjMtNGNjNC05MzQzLTRmMjY4MzBlMDY5NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEABALCwsMCxAMDBAXDw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoXHh4jJSclIx4vLzMzLy9AQEBAQEBAQEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoaJjAjHh4eHiMwKy4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/AABEIACAAIAMBIgACEQEDEQH/xABgAAEAAwEAAAAAAAAAAAAAAAAABAUHCAEBAAAAAAAAAAAAAAAAAAAAABAAAQMCAgsAAAAAAAAAAAAAAAECBBEDEgYhMRODo7PTVAUWNhEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8Az8AAdAAAAAAI8+fE8dEuTZtzZR7VMb6OdTE5GJoYirrUp/e8qd9wb3TGe/lJ2551sx8D/9k='; +const TEST_IMG_JPG = + '/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAeAAD/4QOBaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0MCA3OS4xNjA0NTEsIDIwMTcvMDUvMDYtMDE6MDg6MjEgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NGY5ODc1OTctZGE2My00Y2M0LTkzNDMtNGYyNjgzMGUwNjk3IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjlDMzI3QkY0N0Q3NTExRThCMTlDOTVDMDc2RDE5MDY5IiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjlDMzI3QkYzN0Q3NTExRThCMTlDOTVDMDc2RDE5MDY5IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NGY5ODc1OTctZGE2My00Y2M0LTkzNDMtNGYyNjgzMGUwNjk3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRmOTg3NTk3LWRhNjMtNGNjNC05MzQzLTRmMjY4MzBlMDY5NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEABALCwsMCxAMDBAXDw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoXHh4jJSclIx4vLzMzLy9AQEBAQEBAQEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoaJjAjHh4eHiMwKy4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/AABEIACAAIAMBIgACEQEDEQH/xABgAAEAAwEAAAAAAAAAAAAAAAAABAUHCAEBAAAAAAAAAAAAAAAAAAAAABAAAQMCAgsAAAAAAAAAAAAAAAECBBEDEgYhMRODo7PTVAUWNhEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8Az8AAdAAAAAAI8+fE8dEuTZtzZR7VMb6OdTE5GJoYirrUp/e8qd9wb3TGe/lJ2551sx8D/9k='; const MJPEG_HOST = '127.0.0.1'; @@ -20,23 +21,24 @@ const MJPEG_HOST = '127.0.0.1'; * it closes the connection * @returns {http.Server} */ -function initMJpegServer (port, intMs = 300, times = 20) { - const server = http.createServer(async function (req, res) { - const mJpegReqHandler = mJpegServer.createReqHandler(req, res); - const jpg = Buffer.from(TEST_IMG_JPG, 'base64'); +function initMJpegServer(port, intMs = 300, times = 20) { + const server = http + .createServer(async function (req, res) { + const mJpegReqHandler = mJpegServer.createReqHandler(req, res); + const jpg = Buffer.from(TEST_IMG_JPG, 'base64'); - // just send the same jpeg over and over - for (let i = 0; i < times; i++) { - await B.delay(intMs); - mJpegReqHandler._write(jpg, null, _.noop); - } - mJpegReqHandler.close(); - }).listen(port); + // just send the same jpeg over and over + for (let i = 0; i < times; i++) { + await B.delay(intMs); + mJpegReqHandler._write(jpg, null, _.noop); + } + mJpegReqHandler.close(); + }) + .listen(port); return server; } - describe('MJpeg Stream (e2e)', function () { let mJpegServer, stream; let serverUrl, port; @@ -89,7 +91,6 @@ describe('MJpeg Stream (e2e)', function () { png.indexOf('iVBOR').should.eql(0); png.length.should.be.above(400); - // now stop the stream and wait some more then assert no new data has come in stream.stop(); await B.delay(1000); @@ -101,5 +102,4 @@ describe('MJpeg Stream (e2e)', function () { stream = new MJpegStream(serverUrl, _.noop); await stream.start(0).should.eventually.be.rejectedWith(/never sent/); }); - }); diff --git a/packages/support/test/e2e/net.e2e.spec.js b/packages/support/test/e2e/net.e2e.spec.js index e86160859..a039694a3 100644 --- a/packages/support/test/e2e/net.e2e.spec.js +++ b/packages/support/test/e2e/net.e2e.spec.js @@ -1,7 +1,6 @@ import path from 'path'; -import { downloadFile } from '../../lib/net'; -import { tempDir, fs } from '../../lib/index'; - +import {downloadFile} from '../../lib/net'; +import {tempDir, fs} from '../../lib/index'; describe('#net', function () { let tmpRoot; @@ -17,10 +16,8 @@ describe('#net', function () { describe('downloadFile()', function () { it('should download file into the target folder', async function () { const dstPath = path.join(tmpRoot, 'download.tmp'); - await downloadFile('https://appium.io/ico/apple-touch-icon-114x114-precomposed.png', - dstPath); + await downloadFile('https://appium.io/ico/apple-touch-icon-114x114-precomposed.png', dstPath); await fs.exists(dstPath).should.eventually.be.true; }); }); - }); diff --git a/packages/support/test/e2e/node.e2e.spec.js b/packages/support/test/e2e/node.e2e.spec.js index bc284adb7..b68a7e607 100644 --- a/packages/support/test/e2e/node.e2e.spec.js +++ b/packages/support/test/e2e/node.e2e.spec.js @@ -1,6 +1,4 @@ -import { node } from '../../lib'; - - +import {node} from '../../lib'; describe('node utilities', function () { describe('requirePackage', function () { @@ -12,7 +10,9 @@ describe('node utilities', function () { await node.requirePackage('npm').should.not.be.rejected; }); it('should fail to find uninstalled package', async function () { - await node.requirePackage('appium-foo-driver').should.eventually.be.rejectedWith(/Unable to load package/); + await node + .requirePackage('appium-foo-driver') + .should.eventually.be.rejectedWith(/Unable to load package/); }); }); }); diff --git a/packages/support/test/e2e/util.e2e.spec.js b/packages/support/test/e2e/util.e2e.spec.js index 127bb6781..71f7b9735 100644 --- a/packages/support/test/e2e/util.e2e.spec.js +++ b/packages/support/test/e2e/util.e2e.spec.js @@ -1,8 +1,7 @@ import B from 'bluebird'; import path from 'path'; import * as util from '../../lib/util'; -import { tempDir, fs } from '../../lib/index'; - +import {tempDir, fs} from '../../lib/index'; describe('#util', function () { let tmpRoot; @@ -35,13 +34,13 @@ describe('#util', function () { let lockFile; let testFile; - async function guardedBehavior (text, msBeforeActing) { + async function guardedBehavior(text, msBeforeActing) { await B.delay(msBeforeActing); await fs.appendFile(testFile, text, 'utf8'); return text; } - async function testFileContents () { + async function testFileContents() { return (await fs.readFile(testFile)).toString('utf8'); } @@ -129,5 +128,4 @@ describe('#util', function () { await p2.should.eventually.eql('world'); }); }); - }); diff --git a/packages/support/test/e2e/zip.e2e.spec.js b/packages/support/test/e2e/zip.e2e.spec.js index f0b29d330..dd6255270 100644 --- a/packages/support/test/e2e/zip.e2e.spec.js +++ b/packages/support/test/e2e/zip.e2e.spec.js @@ -1,12 +1,13 @@ import path from 'path'; import * as zip from '../../lib/zip'; -import { tempDir, fs } from '../../lib/index'; -import { MockReadWriteStream } from '../helpers'; - +import {tempDir, fs} from '../../lib/index'; +import {MockReadWriteStream} from '../helpers'; describe('#zip', function () { - - const optionMap = new Map([['native JS unzip', {}], ['system unzip', {useSystemUnzip: true}]]); + const optionMap = new Map([ + ['native JS unzip', {}], + ['system unzip', {useSystemUnzip: true}], + ]); optionMap.forEach((options, desc) => { describe(desc, function () { @@ -17,7 +18,8 @@ describe('#zip', function () { beforeEach(async function () { assetsPath = await tempDir.openDir(); tmpRoot = await tempDir.openDir(); - const zippedBase64 = 'UEsDBAoAAAAAALlzk0oAAAAAAAAAAAAAAAAJABAAdW56aXBwZWQvVVgMANBO+VjO1vdY9QEUAFBLAwQKAAAAAADAc5NKAAAAAAAAAAAAAAAAEgAQAHVuemlwcGVkL3Rlc3QtZGlyL1VYDADQTvlY19b3WPUBFABQSwMEFAAIAAgAwnOTSgAAAAAAAAAAAAAAABcAEAB1bnppcHBlZC90ZXN0LWRpci9hLnR4dFVYDACDTvlY3Nb3WPUBFADzSM3JyVcIzy/KSQEAUEsHCFaxF0oNAAAACwAAAFBLAwQUAAgACADEc5NKAAAAAAAAAAAAAAAAFwAQAHVuemlwcGVkL3Rlc3QtZGlyL2IudHh0VVgMAINO+Vjf1vdY9QEUAHPLz1dwSiwCAFBLBwhIfrZJCQAAAAcAAABQSwECFQMKAAAAAAC5c5NKAAAAAAAAAAAAAAAACQAMAAAAAAAAAABA7UEAAAAAdW56aXBwZWQvVVgIANBO+VjO1vdYUEsBAhUDCgAAAAAAwHOTSgAAAAAAAAAAAAAAABIADAAAAAAAAAAAQO1BNwAAAHVuemlwcGVkL3Rlc3QtZGlyL1VYCADQTvlY19b3WFBLAQIVAxQACAAIAMJzk0pWsRdKDQAAAAsAAAAXAAwAAAAAAAAAAECkgXcAAAB1bnppcHBlZC90ZXN0LWRpci9hLnR4dFVYCACDTvlY3Nb3WFBLAQIVAxQACAAIAMRzk0pIfrZJCQAAAAcAAAAXAAwAAAAAAAAAAECkgdkAAAB1bnppcHBlZC90ZXN0LWRpci9iLnR4dFVYCACDTvlY39b3WFBLBQYAAAAABAAEADEBAAA3AQAAAAA='; + const zippedBase64 = + 'UEsDBAoAAAAAALlzk0oAAAAAAAAAAAAAAAAJABAAdW56aXBwZWQvVVgMANBO+VjO1vdY9QEUAFBLAwQKAAAAAADAc5NKAAAAAAAAAAAAAAAAEgAQAHVuemlwcGVkL3Rlc3QtZGlyL1VYDADQTvlY19b3WPUBFABQSwMEFAAIAAgAwnOTSgAAAAAAAAAAAAAAABcAEAB1bnppcHBlZC90ZXN0LWRpci9hLnR4dFVYDACDTvlY3Nb3WPUBFADzSM3JyVcIzy/KSQEAUEsHCFaxF0oNAAAACwAAAFBLAwQUAAgACADEc5NKAAAAAAAAAAAAAAAAFwAQAHVuemlwcGVkL3Rlc3QtZGlyL2IudHh0VVgMAINO+Vjf1vdY9QEUAHPLz1dwSiwCAFBLBwhIfrZJCQAAAAcAAABQSwECFQMKAAAAAAC5c5NKAAAAAAAAAAAAAAAACQAMAAAAAAAAAABA7UEAAAAAdW56aXBwZWQvVVgIANBO+VjO1vdYUEsBAhUDCgAAAAAAwHOTSgAAAAAAAAAAAAAAABIADAAAAAAAAAAAQO1BNwAAAHVuemlwcGVkL3Rlc3QtZGlyL1VYCADQTvlY19b3WFBLAQIVAxQACAAIAMJzk0pWsRdKDQAAAAsAAAAXAAwAAAAAAAAAAECkgXcAAAB1bnppcHBlZC90ZXN0LWRpci9hLnR4dFVYCACDTvlY3Nb3WFBLAQIVAxQACAAIAMRzk0pIfrZJCQAAAAcAAAAXAAwAAAAAAAAAAECkgdkAAAB1bnppcHBlZC90ZXN0LWRpci9iLnR4dFVYCACDTvlY39b3WFBLBQYAAAAABAAEADEBAAA3AQAAAAA='; zippedFilePath = path.resolve(tmpRoot, 'zipped.zip'); await fs.writeFile(zippedFilePath, zippedBase64, 'base64'); await zip.extractAllTo(zippedFilePath, assetsPath, options); @@ -25,7 +27,7 @@ describe('#zip', function () { afterEach(async function () { for (const tmpPath of [assetsPath, tmpRoot]) { - if (!await fs.exists(tmpPath)) { + if (!(await fs.exists(tmpPath))) { continue; } await fs.rimraf(tmpPath); @@ -34,8 +36,12 @@ describe('#zip', function () { describe('extractAllTo()', function () { it('should extract contents of a .zip file to a directory', async function () { - await fs.readFile(path.resolve(assetsPath, 'unzipped', 'test-dir', 'a.txt'), {encoding: 'utf8'}).should.eventually.equal('Hello World'); - await fs.readFile(path.resolve(assetsPath, 'unzipped', 'test-dir', 'b.txt'), {encoding: 'utf8'}).should.eventually.equal('Foo Bar'); + await fs + .readFile(path.resolve(assetsPath, 'unzipped', 'test-dir', 'a.txt'), {encoding: 'utf8'}) + .should.eventually.equal('Hello World'); + await fs + .readFile(path.resolve(assetsPath, 'unzipped', 'test-dir', 'b.txt'), {encoding: 'utf8'}) + .should.eventually.equal('Foo Bar'); }); }); @@ -47,7 +53,9 @@ describe('#zip', function () { await zip.assertValidZip('blabla').should.eventually.be.rejected; }); it('should throw an error if the file is invalid', async function () { - await zip.assertValidZip(path.resolve(assetsPath, 'unzipped', 'test-dir', 'a.txt')).should.eventually.be.rejected; + await zip.assertValidZip( + path.resolve(assetsPath, 'unzipped', 'test-dir', 'a.txt') + ).should.eventually.be.rejected; }); }); @@ -67,10 +75,12 @@ describe('#zip', function () { // If it's a file, test that we can extract it to a temporary directory and that the contents are correct if (expectedEntries[i].contents) { await extractEntryTo(tmpRoot); - await fs.readFile(path.resolve(tmpRoot, entry.fileName), { - flags: 'r', - encoding: 'utf8' - }).should.eventually.equal(expectedEntries[i].contents); + await fs + .readFile(path.resolve(tmpRoot, entry.fileName), { + flags: 'r', + encoding: 'utf8', + }) + .should.eventually.equal(expectedEntries[i].contents); } i++; }); @@ -78,7 +88,8 @@ describe('#zip', function () { it('should stop iterating zipFile if onEntry callback returns false', async function () { let i = 0; - await zip.readEntries(zippedFilePath, async () => { // eslint-disable-line require-await + // eslint-disable-next-line require-await + await zip.readEntries(zippedFilePath, async () => { i++; return false; }); @@ -86,7 +97,10 @@ describe('#zip', function () { }); it('should be rejected if it uses a non-zip file', async function () { - let promise = zip.readEntries(path.resolve(assetsPath, 'unzipped', 'test-dir', 'a.txt'), async () => {}); + let promise = zip.readEntries( + path.resolve(assetsPath, 'unzipped', 'test-dir', 'a.txt'), + async () => {} + ); await promise.should.eventually.be.rejected; }); }); @@ -102,15 +116,23 @@ describe('#zip', function () { await fs.writeFile(path.resolve(tmpRoot, 'test.zip'), buffer); // Unzip the file and test that it has the same contents as the directory that was zipped - await zip.extractAllTo(path.resolve(tmpRoot, 'test.zip'), path.resolve(tmpRoot, 'output'), { - fileNamesEncoding: 'utf8' - }); - await fs.readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'a.txt'), { - encoding: 'utf8' - }).should.eventually.equal('Hello World'); - await fs.readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'b.txt'), { - encoding: 'utf8' - }).should.eventually.equal('Foo Bar'); + await zip.extractAllTo( + path.resolve(tmpRoot, 'test.zip'), + path.resolve(tmpRoot, 'output'), + { + fileNamesEncoding: 'utf8', + } + ); + await fs + .readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'a.txt'), { + encoding: 'utf8', + }) + .should.eventually.equal('Hello World'); + await fs + .readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'b.txt'), { + encoding: 'utf8', + }) + .should.eventually.equal('Foo Bar'); }); it('should convert a local folder to an in-memory base64-encoded zip buffer', async function () { @@ -119,35 +141,50 @@ describe('#zip', function () { encodeToBase64: true, }); - await fs.writeFile(path.resolve(tmpRoot, 'test.zip'), Buffer.from(buffer.toString(), 'base64')); + await fs.writeFile( + path.resolve(tmpRoot, 'test.zip'), + Buffer.from(buffer.toString(), 'base64') + ); // Unzip the file and test that it has the same contents as the directory that was zipped - await zip.extractAllTo(path.resolve(tmpRoot, 'test.zip'), path.resolve(tmpRoot, 'output')); - await fs.readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'a.txt'), { - encoding: 'utf8' - }).should.eventually.equal('Hello World'); - await fs.readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'b.txt'), { - encoding: 'utf8' - }).should.eventually.equal('Foo Bar'); + await zip.extractAllTo( + path.resolve(tmpRoot, 'test.zip'), + path.resolve(tmpRoot, 'output') + ); + await fs + .readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'a.txt'), { + encoding: 'utf8', + }) + .should.eventually.equal('Hello World'); + await fs + .readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'b.txt'), { + encoding: 'utf8', + }) + .should.eventually.equal('Foo Bar'); }); it('should be rejected if use a bad path', async function () { - await zip.toInMemoryZip(path.resolve(assetsPath, 'bad_path')) + await zip + .toInMemoryZip(path.resolve(assetsPath, 'bad_path')) .should.be.rejectedWith(/no such/i); }); it('should be rejected if max size is exceeded', async function () { const testFolder = path.resolve(assetsPath, 'unzipped'); - await zip.toInMemoryZip(testFolder, { - maxSize: 1, - }).should.be.rejectedWith(/must not be greater/); + await zip + .toInMemoryZip(testFolder, { + maxSize: 1, + }) + .should.be.rejectedWith(/must not be greater/); }); }); describe('_extractEntryTo()', function () { let entry, mockZipFile, mockZipStream; beforeEach(async function () { - entry = {fileName: path.resolve(await tempDir.openDir(), 'temp', 'file')}; + entry = { + fileName: path.resolve(await tempDir.openDir(), 'temp', 'file'), + }; mockZipStream = new MockReadWriteStream(); mockZipFile = { openReadStream: (entry, cb) => cb(null, mockZipStream), // eslint-disable-line promise/prefer-await-to-callbacks @@ -167,7 +204,9 @@ describe('#zip', function () { mockZipStream.end(); writeStream.end(); }; - await zip._extractEntryTo(mockZipFile, entry).should.be.rejectedWith('write stream error'); + await zip + ._extractEntryTo(mockZipFile, entry) + .should.be.rejectedWith('write stream error'); }); }); @@ -181,16 +220,18 @@ describe('#zip', function () { // Unzip the file and test that it has the same contents as the directory that was zipped await zip.extractAllTo(dstPath, path.resolve(tmpRoot, 'output')); - await fs.readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'a.txt'), { - encoding: 'utf8' - }).should.eventually.equal('Hello World'); - await fs.readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'b.txt'), { - encoding: 'utf8' - }).should.eventually.equal('Foo Bar'); + await fs + .readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'a.txt'), { + encoding: 'utf8', + }) + .should.eventually.equal('Hello World'); + await fs + .readFile(path.resolve(tmpRoot, 'output', 'test-dir', 'b.txt'), { + encoding: 'utf8', + }) + .should.eventually.equal('Foo Bar'); }); }); - - }); }); @@ -201,15 +242,18 @@ describe('#zip', function () { assetsPath = await tempDir.openDir(); tmpRoot = await tempDir.openDir(); - const zippedBase64 = 'UEsDBBQACAAIABF8/EYAAAAAAAAAABoAAAATACAAa2Fuamkt5q2j5LiW5LiVLmFwcFVUDQAHAgO4VVpX+GBZV/hgdXgLAAEE9QEAAAQUAAAAK8nILFYAorz8EoWi1MScnEqFxDyFxIICLgBQSwcIR93jPhoAAAAaAAAAUEsBAhQDFAAIAAgAEXz8Rkfd4z4aAAAAGgAAABMAIAAAAAAAAAAAAKSBAAAAAGthbmppLeato+S4luS4lS5hcHBVVA0ABwIDuFVaV/hgWVf4YHV4CwABBPUBAAAEFAAAAFBLBQYAAAAAAQABAGEAAAB7AAAAAAA='; + const zippedBase64 = + 'UEsDBBQACAAIABF8/EYAAAAAAAAAABoAAAATACAAa2Fuamkt5q2j5LiW5LiVLmFwcFVUDQAHAgO4VVpX+GBZV/hgdXgLAAEE9QEAAAQUAAAAK8nILFYAorz8EoWi1MScnEqFxDyFxIICLgBQSwcIR93jPhoAAAAaAAAAUEsBAhQDFAAIAAgAEXz8Rkfd4z4aAAAAGgAAABMAIAAAAAAAAAAAAKSBAAAAAGthbmppLeato+S4luS4lS5hcHBVVA0ABwIDuFVaV/hgWVf4YHV4CwABBPUBAAAEFAAAAFBLBQYAAAAAAQABAGEAAAB7AAAAAAA='; zippedFilePath = path.resolve(tmpRoot, 'zipped.zip'); await fs.writeFile(zippedFilePath, zippedBase64, 'base64'); - await zip.extractAllTo(zippedFilePath, assetsPath, {useSystemUnzip: true}); + await zip.extractAllTo(zippedFilePath, assetsPath, { + useSystemUnzip: true, + }); }); afterEach(async function () { for (const tmpPath of [assetsPath, tmpRoot]) { - if (!await fs.exists(tmpPath)) { + if (!(await fs.exists(tmpPath))) { continue; } await fs.rimraf(tmpPath); @@ -219,7 +263,7 @@ describe('#zip', function () { it('should retain the proper filenames', async function () { const expectedPath = path.join(assetsPath, 'kanji-正世丕.app'); // we cannot use the `should` syntax because `fs.exists` resolves to a primitive (boolean) - if (!await fs.exists(expectedPath)) { + if (!(await fs.exists(expectedPath))) { throw new chai.AssertionError(`Expected ${expectedPath} to exist, but it does not`); } }); diff --git a/packages/support/test/helpers.js b/packages/support/test/helpers.js index 60f5b45e5..6b70875f8 100644 --- a/packages/support/test/helpers.js +++ b/packages/support/test/helpers.js @@ -1,25 +1,25 @@ -import { EventEmitter } from 'events'; +import {EventEmitter} from 'events'; import rewiremock, {addPlugin, overrideEntryPoint, plugins} from 'rewiremock'; overrideEntryPoint(module); addPlugin(plugins.nodejs); class MockReadWriteStream extends EventEmitter { - resume () {} + resume() {} - pause () {} + pause() {} - setEncoding () {} + setEncoding() {} - flush () {} + flush() {} - write (msg) { + write(msg) { this.emit('data', msg); } - end () { + end() { this.emit('end'); this.emit('finish'); } } -export { MockReadWriteStream, rewiremock }; +export {MockReadWriteStream, rewiremock}; diff --git a/packages/support/test/mocks.js b/packages/support/test/mocks.js index 9560b7715..0f3837713 100644 --- a/packages/support/test/mocks.js +++ b/packages/support/test/mocks.js @@ -6,14 +6,14 @@ */ import path from 'path'; -import { createSandbox } from 'sinon'; +import {createSandbox} from 'sinon'; -export function initMocks (sandbox = createSandbox()) { +export function initMocks(sandbox = createSandbox()) { const MockResolveFrom = /** @type {MockResolveFrom} */ ( sandbox.stub().callsFake((cwd, id) => path.join(cwd, id)) ); - const MockPkgDir = /** @type {MockPkgDir} */(sandbox.stub().resolvesArg(0)); + const MockPkgDir = /** @type {MockPkgDir} */ (sandbox.stub().resolvesArg(0)); const MockReadPkg = /** @type {MockReadPkg} */ ( sandbox.stub().callsFake(async () => MockReadPkg.__pkg) @@ -31,7 +31,7 @@ export function initMocks (sandbox = createSandbox()) { access: sandbox.stub().resolves(true), }); - const MockTeenProcess = /** @type {MockTeenProcess} */({ + const MockTeenProcess = /** @type {MockTeenProcess} */ ({ exec: sandbox.stub().callsFake(async () => ({ stdout: MockTeenProcess.__stdout, stderr: MockTeenProcess.__stderr, diff --git a/packages/support/test/unit/env.spec.js b/packages/support/test/unit/env.spec.js index be2ab6db1..1e7999fa1 100644 --- a/packages/support/test/unit/env.spec.js +++ b/packages/support/test/unit/env.spec.js @@ -29,8 +29,7 @@ describe('env', function () { beforeEach(function () { let overrides; - ({MockPkgDir, MockReadPkg, MockTeenProcess, sandbox, overrides} = - initMocks()); + ({MockPkgDir, MockReadPkg, MockTeenProcess, sandbox, overrides} = initMocks()); // ensure an APPIUM_HOME in the environment does not befoul our tests envAppiumHome = process.env.APPIUM_HOME; @@ -47,7 +46,7 @@ describe('env', function () { it('should return a path relative to the default APPIUM_HOME', async function () { expect(await env.resolveManifestPath()).to.equal( - path.join(env.DEFAULT_APPIUM_HOME, env.MANIFEST_RELATIVE_PATH), + path.join(env.DEFAULT_APPIUM_HOME, env.MANIFEST_RELATIVE_PATH) ); }); }); @@ -55,13 +54,8 @@ describe('env', function () { describe('when provided an explicit APPIUM_HOME', function () { describe('when a manifest file exists there', function () { it('it should return the existing path', async function () { - expect( - await env.resolveManifestPath('/somewhere/over/the/rainbow'), - ).to.equal( - path.join( - '/somewhere/over/the/rainbow', - env.MANIFEST_RELATIVE_PATH, - ), + expect(await env.resolveManifestPath('/somewhere/over/the/rainbow')).to.equal( + path.join('/somewhere/over/the/rainbow', env.MANIFEST_RELATIVE_PATH) ); }); }); @@ -71,10 +65,7 @@ describe('env', function () { describe('resolveAppiumHome()', function () { describe('when param is not absolute', function () { it('should reject', async function () { - await expect(env.resolveAppiumHome('foo')).to.be.rejectedWith( - TypeError, - /absolute/i, - ); + await expect(env.resolveAppiumHome('foo')).to.be.rejectedWith(TypeError, /absolute/i); }); }); @@ -84,9 +75,7 @@ describe('env', function () { }); it('should resolve APPIUM_HOME from env', async function () { - await expect(env.resolveAppiumHome()).to.eventually.equal( - process.env.APPIUM_HOME, - ); + await expect(env.resolveAppiumHome()).to.eventually.equal(process.env.APPIUM_HOME); }); }); @@ -101,9 +90,9 @@ describe('env', function () { }); it('should resolve with DEFAULT_APPIUM_HOME', async function () { - await expect( - env.resolveAppiumHome('/somewhere'), - ).to.eventually.equal(env.DEFAULT_APPIUM_HOME); + await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal( + env.DEFAULT_APPIUM_HOME + ); }); }); @@ -114,9 +103,9 @@ describe('env', function () { }); it('should resolve with DEFAULT_APPIUM_HOME', async function () { - await expect( - env.resolveAppiumHome('/somewhere'), - ).to.eventually.equal(env.DEFAULT_APPIUM_HOME); + await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal( + env.DEFAULT_APPIUM_HOME + ); }); }); @@ -139,9 +128,7 @@ describe('env', function () { }); it('should resolve with the identity', async function () { - await expect( - env.resolveAppiumHome('/somewhere'), - ).to.eventually.equal('/somewhere'); + await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal('/somewhere'); }); }); @@ -163,9 +150,9 @@ describe('env', function () { }); }); it('should resolve with DEFAULT_APPIUM_HOME', async function () { - await expect( - env.resolveAppiumHome('/somewhere'), - ).to.eventually.equal(env.DEFAULT_APPIUM_HOME); + await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal( + env.DEFAULT_APPIUM_HOME + ); }); }); @@ -187,9 +174,9 @@ describe('env', function () { }); }); it('should resolve with DEFAULT_APPIUM_HOME', async function () { - await expect( - env.resolveAppiumHome('/somewhere'), - ).to.eventually.equal(env.DEFAULT_APPIUM_HOME); + await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal( + env.DEFAULT_APPIUM_HOME + ); }); }); @@ -212,9 +199,9 @@ describe('env', function () { }); it('should resolve with DEFAULT_APPIUM_HOME', async function () { - await expect( - env.resolveAppiumHome('/somewhere'), - ).to.eventually.equal(env.DEFAULT_APPIUM_HOME); + await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal( + env.DEFAULT_APPIUM_HOME + ); }); }); }); @@ -228,7 +215,7 @@ describe('env', function () { it('should resolve with DEFAULT_APPIUM_HOME', async function () { await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal( - env.DEFAULT_APPIUM_HOME, + env.DEFAULT_APPIUM_HOME ); }); }); @@ -240,7 +227,7 @@ describe('env', function () { it('should resolve with DEFAULT_APPIUM_HOME', async function () { await expect(env.resolveAppiumHome('/somewhere')).to.eventually.equal( - env.DEFAULT_APPIUM_HOME, + env.DEFAULT_APPIUM_HOME ); }); }); @@ -268,9 +255,7 @@ describe('env', function () { }); it('should resolve `false``', async function () { - await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal( - false, - ); + await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(false); }); }); @@ -283,54 +268,48 @@ describe('env', function () { describe('when the `appium` dependency spec begins with `file:`', function () { beforeEach(function () { - MockReadPkg.resolves({dependencies: {'appium': 'file:packges/appium'}}); + MockReadPkg.resolves({ + dependencies: {appium: 'file:packges/appium'}, + }); }); it('should resolve `false`', async function () { - await expect( - env.hasAppiumDependency('/somewhere'), - ).to.eventually.equal(false); + await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(false); }); }); describe('when `appium` dep is current`', function () { beforeEach(function () { MockReadPkg.resolves({ - devDependencies: {'appium': '2.0.0'} + devDependencies: {appium: '2.0.0'}, }); }); it('should resolve `true`', async function () { - await expect( - env.hasAppiumDependency('/somewhere'), - ).to.eventually.equal(true); + await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(true); }); }); describe('when `appium` dep is v1.x', function () { beforeEach(function () { MockReadPkg.resolves({ - optionalDependencies: {'appium': '1.x'} + optionalDependencies: {appium: '1.x'}, }); }); it('should resolve `false`', async function () { - await expect( - env.hasAppiumDependency('/somewhere'), - ).to.eventually.equal(false); + await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(false); }); }); describe('when `appium` dep is v0.x', function () { beforeEach(function () { MockReadPkg.resolves({ - dependencies: {'appium': '0.x'} + dependencies: {appium: '0.x'}, }); }); it('should resolve `false`', async function () { - await expect( - env.hasAppiumDependency('/somewhere'), - ).to.eventually.equal(false); + await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(false); }); }); }); @@ -355,9 +334,7 @@ describe('env', function () { }); it('should resolve `true`', async function () { - await expect( - env.hasAppiumDependency('/somewhere'), - ).to.eventually.equal(true); + await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(true); }); }); @@ -379,9 +356,7 @@ describe('env', function () { }); }); it('should resolve `false`', async function () { - await expect( - env.hasAppiumDependency('/somewhere'), - ).to.eventually.equal(false); + await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(false); }); }); @@ -403,9 +378,7 @@ describe('env', function () { }); }); it('should resolve `false`', async function () { - await expect( - env.hasAppiumDependency('/somewhere'), - ).to.eventually.equal(false); + await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(false); }); }); @@ -428,9 +401,7 @@ describe('env', function () { }); it('should resolve `false`', async function () { - await expect( - env.hasAppiumDependency('/somewhere'), - ).to.eventually.equal(false); + await expect(env.hasAppiumDependency('/somewhere')).to.eventually.equal(false); }); }); }); diff --git a/packages/support/test/unit/fs.spec.js b/packages/support/test/unit/fs.spec.js index 9e72cb7ca..f38a081fe 100644 --- a/packages/support/test/unit/fs.spec.js +++ b/packages/support/test/unit/fs.spec.js @@ -1,7 +1,7 @@ -import { fs, tempDir } from '../../lib/index.js'; +import {fs, tempDir} from '../../lib/index.js'; import path from 'path'; -import { createSandbox } from 'sinon'; -import { exec } from 'teen_process'; +import {createSandbox} from 'sinon'; +import {exec} from 'teen_process'; import _ from 'lodash'; // TODO: normalize test organization @@ -146,8 +146,7 @@ describe('fs', function () { npmPath.should.equal(systemNpmPath); }); it('should fail gracefully', async function () { - await fs.which('something_that_does_not_exist') - .should.eventually.be.rejected; + await fs.which('something_that_does_not_exist').should.eventually.be.rejected; }); }); it('glob()', async function () { @@ -159,19 +158,23 @@ describe('fs', function () { describe('walkDir()', function () { it('walkDir recursive', async function () { - await chai.expect(fs.walkDir(__dirname, true, (item) => item.endsWith('logger/helpers.js'))).to.eventually.not.be.null; + await chai.expect(fs.walkDir(__dirname, true, (item) => item.endsWith('logger/helpers.js'))) + .to.eventually.not.be.null; }); it('should walk all elements recursive', async function () { - await chai.expect(fs.walkDir(path.join(__dirname, '..', 'e2e', 'fixture'), true, _.noop)).to.eventually.be.null; + await chai.expect(fs.walkDir(path.join(__dirname, '..', 'e2e', 'fixture'), true, _.noop)).to + .eventually.be.null; }); it('should throw error through callback', async function () { const err = new Error('Callback error'); const stub = sandbox.stub().rejects(err); - await (fs.walkDir(__dirname, true, stub)).should.eventually.be.rejectedWith(err); + await fs.walkDir(__dirname, true, stub).should.eventually.be.rejectedWith(err); stub.should.have.been.calledOnce; }); it('should traverse non-recursively', async function () { - const filePath = await fs.walkDir(__dirname, false, (item) => item.endsWith('logger/helpers.js')); + const filePath = await fs.walkDir(__dirname, false, (item) => + item.endsWith('logger/helpers.js') + ); _.isNil(filePath).should.be.true; }); }); diff --git a/packages/support/test/unit/index.spec.js b/packages/support/test/unit/index.spec.js index a4bd99f4c..c8b397934 100644 --- a/packages/support/test/unit/index.spec.js +++ b/packages/support/test/unit/index.spec.js @@ -1,7 +1,6 @@ - import AppiumSupport from '../../lib/index.js'; -let { system, tempDir, util } = AppiumSupport; +let {system, tempDir, util} = AppiumSupport; describe('index', function () { describe('default', function () { diff --git a/packages/support/test/unit/log-internals.spec.js b/packages/support/test/unit/log-internals.spec.js index c7f846d1a..004e21bca 100644 --- a/packages/support/test/unit/log-internals.spec.js +++ b/packages/support/test/unit/log-internals.spec.js @@ -1,14 +1,10 @@ -import { fs } from '../../lib/index'; +import {fs} from '../../lib/index'; import os from 'os'; import path from 'path'; -import { SecureValuesPreprocessor } from '../../lib/log-internal'; - +import {SecureValuesPreprocessor} from '../../lib/log-internal'; const CONFIG_PATH = path.resolve(os.tmpdir(), 'rules.json'); - - - describe('Log Internals', function () { let preprocessor; @@ -17,9 +13,7 @@ describe('Log Internals', function () { }); it('should preprocess a string and make replacements', async function () { - const issues = await preprocessor.loadRules([ - 'yolo', - ]); + const issues = await preprocessor.loadRules(['yolo']); issues.length.should.eql(0); preprocessor.rules.length.should.eql(1); const replacer = preprocessor.rules[0].replacer; @@ -27,33 +21,32 @@ describe('Log Internals', function () { }); it('should preprocess a string and make replacements with multiple simple rules', async function () { - const issues = await preprocessor.loadRules([ - 'yolo', - 'yo', - ]); + const issues = await preprocessor.loadRules(['yolo', 'yo']); issues.length.should.eql(0); preprocessor.rules.length.should.eql(2); const replacer = preprocessor.rules[0].replacer; - preprocessor.preprocess(':yolo" yo Yolo yyolo').should.eql(`:${replacer}" ${replacer} Yolo yyolo`); + preprocessor + .preprocess(':yolo" yo Yolo yyolo') + .should.eql(`:${replacer}" ${replacer} Yolo yyolo`); }); it('should preprocess a string and make replacements with multiple complex rules', async function () { const replacer2 = '***'; const issues = await preprocessor.loadRules([ - { text: 'yolo', flags: 'i' }, - { pattern: '^:', replacer: replacer2 }, + {text: 'yolo', flags: 'i'}, + {pattern: '^:', replacer: replacer2}, ]); issues.length.should.eql(0); preprocessor.rules.length.should.eql(2); const replacer = preprocessor.rules[0].replacer; - preprocessor.preprocess(':yolo" yo Yolo yyolo').should.eql(`${replacer2}${replacer}" yo ${replacer} yyolo`); + preprocessor + .preprocess(':yolo" yo Yolo yyolo') + .should.eql(`${replacer2}${replacer}" yo ${replacer} yyolo`); }); it(`should preprocess a string and apply a rule where 'pattern' has priority over 'text'`, async function () { const replacer = '***'; - const issues = await preprocessor.loadRules([ - { pattern: '^:', text: 'yo', replacer }, - ]); + const issues = await preprocessor.loadRules([{pattern: '^:', text: 'yo', replacer}]); issues.length.should.eql(0); preprocessor.rules.length.should.eql(1); preprocessor.preprocess(':yolo" yo Yolo yyolo').should.eql(`${replacer}yolo" yo Yolo yyolo`); @@ -62,21 +55,23 @@ describe('Log Internals', function () { it('should preprocess a string and make replacements with multiple complex rules and issues', async function () { const replacer2 = '***'; const issues = await preprocessor.loadRules([ - { text: 'yolo', flags: 'i' }, - { pattern: '^:(', replacer: replacer2 }, + {text: 'yolo', flags: 'i'}, + {pattern: '^:(', replacer: replacer2}, ]); issues.length.should.eql(1); preprocessor.rules.length.should.eql(1); const replacer = preprocessor.rules[0].replacer; - preprocessor.preprocess(':yolo" yo Yolo yyolo').should.eql(`:${replacer}" yo ${replacer} yyolo`); + preprocessor + .preprocess(':yolo" yo Yolo yyolo') + .should.eql(`:${replacer}" yo ${replacer} yyolo`); }); it('should leave the string unchanged if all rules have issues', async function () { const replacer2 = '***'; const issues = await preprocessor.loadRules([ null, - { flags: 'i' }, - { pattern: '^:(', replacer: replacer2 }, + {flags: 'i'}, + {pattern: '^:(', replacer: replacer2}, ]); issues.length.should.eql(3); preprocessor.rules.length.should.eql(0); diff --git a/packages/support/test/unit/logger/helpers.js b/packages/support/test/unit/logger/helpers.js index 3c18f39c3..bb0b1968b 100644 --- a/packages/support/test/unit/logger/helpers.js +++ b/packages/support/test/unit/logger/helpers.js @@ -1,29 +1,28 @@ import sinon from 'sinon'; import _ from 'lodash'; -import { logger } from '../../../lib'; - +import {logger} from '../../../lib'; let sandbox; -function setupWriters () { +function setupWriters() { sandbox = sinon.createSandbox(); return { stdout: sandbox.spy(process.stdout, 'write'), - stderr: sandbox.spy(process.stderr, 'write') + stderr: sandbox.spy(process.stderr, 'write'), }; } -function getDynamicLogger (testingMode, forceLogs, prefix = null) { +function getDynamicLogger(testingMode, forceLogs, prefix = null) { process.env._TESTING = testingMode ? '1' : '0'; process.env._FORCE_LOGS = forceLogs ? '1' : '0'; return logger.getLogger(prefix); } -function restoreWriters () { +function restoreWriters() { sandbox.restore(); } -function someoneHadOutput (writers, output) { +function someoneHadOutput(writers, output) { let hadOutput = false; let matchOutput = sinon.match(function (value) { return value && value.indexOf(output) >= 0; @@ -38,19 +37,22 @@ function someoneHadOutput (writers, output) { return hadOutput; } -function assertOutputContains (writers, output) { +function assertOutputContains(writers, output) { if (!someoneHadOutput(writers, output)) { throw new Error(`Expected something to have been called with: '${output}'`); } } -function assertOutputDoesntContain (writers, output) { +function assertOutputDoesntContain(writers, output) { if (someoneHadOutput(writers, output)) { throw new Error(`Expected nothing to have been called with: '${output}'`); } } export { - setupWriters, restoreWriters, assertOutputContains, assertOutputDoesntContain, + setupWriters, + restoreWriters, + assertOutputContains, + assertOutputDoesntContain, getDynamicLogger, }; diff --git a/packages/support/test/unit/logger/logger-force.spec.js b/packages/support/test/unit/logger/logger-force.spec.js index 35cb16ee8..ba01e330b 100644 --- a/packages/support/test/unit/logger/logger-force.spec.js +++ b/packages/support/test/unit/logger/logger-force.spec.js @@ -1,7 +1,6 @@ // transpile:mocha -import { getDynamicLogger, restoreWriters, setupWriters, - assertOutputContains } from './helpers'; +import {getDynamicLogger, restoreWriters, setupWriters, assertOutputContains} from './helpers'; describe('logger with force log', function () { let writers, log; @@ -30,7 +29,9 @@ describe('logger with force log', function () { assertOutputContains(writers, 'warn'); log.error('error'); assertOutputContains(writers, 'error'); - (() => { log.errorAndThrow('msg'); }).should.throw('msg'); + (() => { + log.errorAndThrow('msg'); + }).should.throw('msg'); assertOutputContains(writers, 'error'); assertOutputContains(writers, 'msg'); }); diff --git a/packages/support/test/unit/logger/logger-normal.spec.js b/packages/support/test/unit/logger/logger-normal.spec.js index dc72f5f4e..5dfe87e32 100644 --- a/packages/support/test/unit/logger/logger-normal.spec.js +++ b/packages/support/test/unit/logger/logger-normal.spec.js @@ -1,7 +1,12 @@ // transpile:mocha -import { getDynamicLogger, restoreWriters, setupWriters, - assertOutputContains, assertOutputDoesntContain } from './helpers'; +import { + getDynamicLogger, + restoreWriters, + setupWriters, + assertOutputContains, + assertOutputDoesntContain, +} from './helpers'; const LOG_LEVELS = ['silly', 'verbose', 'info', 'http', 'warn', 'error']; @@ -24,8 +29,12 @@ describe('normal logger', function () { } }); it('throw should not rewrite log levels outside of testing and throw error', function () { - (() => { log.errorAndThrow('msg1'); }).should.throw('msg1'); - (() => { log.errorAndThrow(new Error('msg2')); }).should.throw('msg2'); + (() => { + log.errorAndThrow('msg1'); + }).should.throw('msg1'); + (() => { + log.errorAndThrow(new Error('msg2')); + }).should.throw('msg2'); assertOutputContains(writers, 'msg1'); assertOutputContains(writers, 'msg2'); }); @@ -77,7 +86,9 @@ describe('normal logger with static prefix', function () { } }); it('throw should not rewrite log levels outside of testing and throw error', function () { - (() => { log.errorAndThrow('msg'); }).should.throw('msg'); + (() => { + log.errorAndThrow('msg'); + }).should.throw('msg'); assertOutputContains(writers, 'error'); assertOutputContains(writers, PREFIX); }); @@ -105,7 +116,9 @@ describe('normal logger with dynamic prefix', function () { } }); it('throw should not rewrite log levels outside of testing and throw error', function () { - (() => { log.errorAndThrow('msg'); }).should.throw('msg'); + (() => { + log.errorAndThrow('msg'); + }).should.throw('msg'); assertOutputContains(writers, 'error'); assertOutputContains(writers, PREFIX); }); diff --git a/packages/support/test/unit/logger/logger-test.spec.js b/packages/support/test/unit/logger/logger-test.spec.js index 445223840..dadb9b789 100644 --- a/packages/support/test/unit/logger/logger-test.spec.js +++ b/packages/support/test/unit/logger/logger-test.spec.js @@ -1,7 +1,6 @@ // transpile:mocha -import { getDynamicLogger, restoreWriters, setupWriters, - assertOutputDoesntContain } from './helpers'; +import {getDynamicLogger, restoreWriters, setupWriters, assertOutputDoesntContain} from './helpers'; describe('test logger', function () { let writers, log; @@ -32,7 +31,9 @@ describe('test logger', function () { log.http(text); log.warn(text); log.error(text); - (() => { log.errorAndThrow(text); }).should.throw(text); + (() => { + log.errorAndThrow(text); + }).should.throw(text); assertOutputDoesntContain(writers, text); }); }); diff --git a/packages/support/test/unit/node.spec.js b/packages/support/test/unit/node.spec.js index e4b29432d..33eb663bb 100644 --- a/packages/support/test/unit/node.spec.js +++ b/packages/support/test/unit/node.spec.js @@ -1,5 +1,5 @@ -import { should } from 'chai'; -import { node } from '../../lib'; +import {should} from 'chai'; +import {node} from '../../lib'; describe('node utilities', function () { describe('getObjectSize', function () { @@ -10,13 +10,15 @@ describe('node utilities', function () { node.getObjectSize(null).should.eql(0); node.getObjectSize({}).should.eql(0); node.getObjectSize(Buffer.from([1, 2, 3])).should.eql(3); - node.getObjectSize({ - 'a': 1, - 'b': 2, - 'c': { - 'd': 4, - } - }).should.eql(32); + node + .getObjectSize({ + a: 1, + b: 2, + c: { + d: 4, + }, + }) + .should.eql(32); }); }); @@ -37,7 +39,7 @@ describe('node utilities', function () { const obj1 = {}; node.deepFreeze(obj1).should.eql(obj1); const obj2 = node.deepFreeze({a: {b: 'c'}}); - should().throw(() => obj2.a.b = 'd'); + should().throw(() => (obj2.a.b = 'd')); node.deepFreeze(1).should.eql(1); should().equal(node.deepFreeze(null), null); const obj3 = [1, {}, 3]; diff --git a/packages/support/test/unit/npm.spec.js b/packages/support/test/unit/npm.spec.js index 0db7d0b16..9449a1226 100644 --- a/packages/support/test/unit/npm.spec.js +++ b/packages/support/test/unit/npm.spec.js @@ -3,10 +3,22 @@ import {NPM} from '../../lib/npm'; describe('npm', function () { - describe('getLatestSafeUpgradeFromVersions()', function () { - const versions1 = ['0.1.0', '0.1.1', '0.2.0', '0.2.5', '1.0.0', '1.0.1', '1.1.5', '1.2.7', - '2.0.0', '1.2.8-beta', '1.2.9-alpha', '1.3.0-rc', '2.0.1-beta']; + const versions1 = [ + '0.1.0', + '0.1.1', + '0.2.0', + '0.2.5', + '1.0.0', + '1.0.1', + '1.1.5', + '1.2.7', + '2.0.0', + '1.2.8-beta', + '1.2.9-alpha', + '1.3.0-rc', + '2.0.1-beta', + ]; const npm = new NPM(); it('should get the latest minor upgrade in a list of versions', function () { npm.getLatestSafeUpgradeFromVersions('0.1.0', versions1).should.eql('0.2.5'); diff --git a/packages/support/test/unit/plist.spec.js b/packages/support/test/unit/plist.spec.js index 373acb8f9..3e7633f46 100644 --- a/packages/support/test/unit/plist.spec.js +++ b/packages/support/test/unit/plist.spec.js @@ -1,7 +1,5 @@ import path from 'path'; -import { plist, tempDir, fs } from '../../lib/index.js'; - - +import {plist, tempDir, fs} from '../../lib/index.js'; const binaryPlistPath = path.join(__dirname, 'assets', 'sample_binary.plist'); const textPlistPath = path.join(__dirname, 'assets', 'sample_text.plist'); @@ -9,7 +7,9 @@ const textPlistPath = path.join(__dirname, 'assets', 'sample_text.plist'); describe('plist', function () { it('should parse plist file as binary', async function () { let content = await plist.parsePlistFile(binaryPlistPath); - content.should.have.property('com.apple.locationd.bundle-/System/Library/PrivateFrameworks/Parsec.framework'); + content.should.have.property( + 'com.apple.locationd.bundle-/System/Library/PrivateFrameworks/Parsec.framework' + ); }); it(`should return an empty object if file doesn't exist and mustExist is set to false`, async function () { @@ -26,7 +26,7 @@ describe('plist', function () { // write some data let updatedFields = { - 'io.appium.test': true + 'io.appium.test': true, }; await plist.updatePlistFile(plistFile, updatedFields, true); @@ -38,12 +38,16 @@ describe('plist', function () { it('should read binary plist', async function () { const content = await fs.readFile(binaryPlistPath); const object = plist.parsePlist(content); - object.should.have.property('com.apple.locationd.bundle-/System/Library/PrivateFrameworks/Parsec.framework'); + object.should.have.property( + 'com.apple.locationd.bundle-/System/Library/PrivateFrameworks/Parsec.framework' + ); }); it('should read text plist', async function () { const content = await fs.readFile(textPlistPath); const object = plist.parsePlist(content); - object.should.have.property('com.apple.locationd.bundle-/System/Library/PrivateFrameworks/Parsec.framework'); + object.should.have.property( + 'com.apple.locationd.bundle-/System/Library/PrivateFrameworks/Parsec.framework' + ); }); }); diff --git a/packages/support/test/unit/process.spec.js b/packages/support/test/unit/process.spec.js index 10a6c2a4e..a7fd5fe02 100644 --- a/packages/support/test/unit/process.spec.js +++ b/packages/support/test/unit/process.spec.js @@ -1,7 +1,7 @@ import * as teenProcess from 'teen_process'; -import { createSandbox } from 'sinon'; -import { process } from '../../lib/index.js'; -import { retryInterval } from 'asyncbox'; +import {createSandbox} from 'sinon'; +import {process} from '../../lib/index.js'; +import {retryInterval} from 'asyncbox'; const SubProcess = teenProcess.SubProcess; @@ -63,15 +63,16 @@ describe('process', function () { await process.killProcess('tail'); // it may take a moment to actually be registered as killed - await retryInterval(10, 100, async () => { // eslint-disable-line require-await + // eslint-disable-next-line require-await + await retryInterval(10, 100, async () => { proc.isRunning.should.be.false; }); }); it('should do nothing if the process does not exist', async function () { proc.isRunning.should.be.true; await process.killProcess('asdfasdfasdf'); - - await retryInterval(10, 100, async () => { // eslint-disable-line require-await + // eslint-disable-next-line require-await + await retryInterval(10, 100, async () => { proc.isRunning.should.be.false; }).should.eventually.be.rejected; }); @@ -85,9 +86,13 @@ describe('process', function () { }); it('should throw an error if pkill fails', async function () { let tpMock = sandbox.mock(teenProcess); - tpMock.expects('exec').twice() - .onFirstCall().returns({stdout: '42\n'}) - .onSecondCall().throws({message: 'Oops', code: 2}); + tpMock + .expects('exec') + .twice() + .onFirstCall() + .returns({stdout: '42\n'}) + .onSecondCall() + .throws({message: 'Oops', code: 2}); await process.killProcess('tail').should.eventually.be.rejectedWith(/Oops/); diff --git a/packages/support/test/unit/system.spec.js b/packages/support/test/unit/system.spec.js index 752c0ea0b..d0c2a51d2 100644 --- a/packages/support/test/unit/system.spec.js +++ b/packages/support/test/unit/system.spec.js @@ -1,11 +1,11 @@ -import { system } from '../../lib/index.js'; +import {system} from '../../lib/index.js'; import os from 'os'; -import { createSandbox } from 'sinon'; +import {createSandbox} from 'sinon'; import * as teen_process from 'teen_process'; import _ from 'lodash'; - -let tpMock, osMock = null; +let tpMock, + osMock = null; let SANDBOX = Symbol(); let mocks = {}; let libs = {teen_process, os, system}; @@ -54,24 +54,42 @@ describe('system', function () { }); it('should return correct version for 10.10.5', async function () { - tpMock.expects('exec').once().withExactArgs('sw_vers', ['-productVersion']).returns({stdout: '10.10.5'}); + tpMock + .expects('exec') + .once() + .withExactArgs('sw_vers', ['-productVersion']) + .returns({stdout: '10.10.5'}); await system.macOsxVersion().should.eventually.equal('10.10'); }); it('should return correct version for 10.12', async function () { - tpMock.expects('exec').once().withExactArgs('sw_vers', ['-productVersion']).returns({stdout: '10.12.0'}); + tpMock + .expects('exec') + .once() + .withExactArgs('sw_vers', ['-productVersion']) + .returns({stdout: '10.12.0'}); await system.macOsxVersion().should.eventually.equal('10.12'); }); it('should return correct version for 10.12 with newline', async function () { - tpMock.expects('exec').once().withExactArgs('sw_vers', ['-productVersion']).returns({stdout: '10.12 \n'}); + tpMock + .expects('exec') + .once() + .withExactArgs('sw_vers', ['-productVersion']) + .returns({stdout: '10.12 \n'}); await system.macOsxVersion().should.eventually.equal('10.12'); }); it("should throw an error if OSX version can't be determined", async function () { let invalidOsx = 'error getting operation system version blabla'; - tpMock.expects('exec').once().withExactArgs('sw_vers', ['-productVersion']).returns({stdout: invalidOsx}); - await system.macOsxVersion().should.eventually.be.rejectedWith(new RegExp(_.escapeRegExp(invalidOsx))); + tpMock + .expects('exec') + .once() + .withExactArgs('sw_vers', ['-productVersion']) + .returns({stdout: invalidOsx}); + await system + .macOsxVersion() + .should.eventually.be.rejectedWith(new RegExp(_.escapeRegExp(invalidOsx))); }); }); @@ -90,7 +108,11 @@ describe('system', function () { it('should return correct architecture if it is a 64 bit Mac/Linux', async function () { mocks.os.expects('type').thrice().returns('Darwin'); - mocks.teen_process.expects('exec').once().withExactArgs('uname', ['-m']).returns({stdout: 'x86_64'}); + mocks.teen_process + .expects('exec') + .once() + .withExactArgs('uname', ['-m']) + .returns({stdout: 'x86_64'}); let arch = await system.arch(); arch.should.equal('64'); mocks[SANDBOX].verify(); @@ -98,7 +120,11 @@ describe('system', function () { it('should return correct architecture if it is a 32 bit Mac/Linux', async function () { mocks.os.expects('type').twice().returns('Linux'); - mocks.teen_process.expects('exec').once().withExactArgs('uname', ['-m']).returns({stdout: 'i686'}); + mocks.teen_process + .expects('exec') + .once() + .withExactArgs('uname', ['-m']) + .returns({stdout: 'i686'}); let arch = await system.arch(); arch.should.equal('32'); mocks[SANDBOX].verify(); diff --git a/packages/support/test/unit/tempdir.spec.js b/packages/support/test/unit/tempdir.spec.js index d8150248b..f24161ed4 100644 --- a/packages/support/test/unit/tempdir.spec.js +++ b/packages/support/test/unit/tempdir.spec.js @@ -1,6 +1,4 @@ - -import { tempDir, fs } from '../../lib/index.js'; - +import {tempDir, fs} from '../../lib/index.js'; describe('tempdir', function () { afterEach(function () { diff --git a/packages/support/test/unit/timing.spec.js b/packages/support/test/unit/timing.spec.js index 1f17b7ad3..2055443b0 100644 --- a/packages/support/test/unit/timing.spec.js +++ b/packages/support/test/unit/timing.spec.js @@ -1,7 +1,6 @@ import _ from 'lodash'; -import { createSandbox } from 'sinon'; -import { timing } from '../../lib'; - +import {createSandbox} from 'sinon'; +import {timing} from '../../lib'; const expect = chai.expect; @@ -44,27 +43,39 @@ describe('timing', function () { _.isNumber(duration.nanos).should.be.true; }); it('should get correct seconds', function () { - processMock.expects('hrtime').twice() - .onFirstCall().returns([12, 12345]) - .onSecondCall().returns([13, 54321]); + processMock + .expects('hrtime') + .twice() + .onFirstCall() + .returns([12, 12345]) + .onSecondCall() + .returns([13, 54321]); const timer = new timing.Timer().start(); const duration = timer.getDuration(); duration.asSeconds.should.eql(13.000054321); }); it('should get correct milliseconds', function () { - processMock.expects('hrtime').twice() - .onFirstCall().returns([12, 12345]) - .onSecondCall().returns([13, 54321]); + processMock + .expects('hrtime') + .twice() + .onFirstCall() + .returns([12, 12345]) + .onSecondCall() + .returns([13, 54321]); const timer = new timing.Timer().start(); const duration = timer.getDuration(); duration.asMilliSeconds.should.eql(13000.054321); }); it('should get correct nanoseconds', function () { - processMock.expects('hrtime').twice() - .onFirstCall().returns([12, 12345]) - .onSecondCall().returns([13, 54321]); + processMock + .expects('hrtime') + .twice() + .onFirstCall() + .returns([12, 12345]) + .onSecondCall() + .returns([13, 54321]); const timer = new timing.Timer().start(); const duration = timer.getDuration(); @@ -72,14 +83,12 @@ describe('timing', function () { }); it('should error if the timer was not started', function () { const timer = new timing.Timer(); - expect(() => timer.getDuration()) - .to.throw('Unable to get duration'); + expect(() => timer.getDuration()).to.throw('Unable to get duration'); }); it('should error if start time is a number', function () { const timer = new timing.Timer(); timer._startTime = 12345; - expect(() => timer.getDuration()) - .to.throw('Unable to get duration'); + expect(() => timer.getDuration()).to.throw('Unable to get duration'); }); }); describe('bigint', function () { @@ -94,14 +103,17 @@ describe('timing', function () { processMock = sandbox.mock(process.hrtime); }); - function setupMocks (once = false) { + function setupMocks(once = false) { if (once) { - processMock.expects('bigint').once() - .onFirstCall().returns(BigInt(1172941153404030)); + processMock.expects('bigint').once().onFirstCall().returns(BigInt(1172941153404030)); } else { - processMock.expects('bigint').twice() - .onFirstCall().returns(BigInt(1172941153404030)) - .onSecondCall().returns(BigInt(1172951164887132)); + processMock + .expects('bigint') + .twice() + .onFirstCall() + .returns(BigInt(1172941153404030)) + .onSecondCall() + .returns(BigInt(1172951164887132)); } } @@ -135,14 +147,12 @@ describe('timing', function () { }); it('should error if the timer was not started', function () { const timer = new timing.Timer(); - expect(() => timer.getDuration()) - .to.throw('Unable to get duration'); + expect(() => timer.getDuration()).to.throw('Unable to get duration'); }); it('should error if passing in a non-bigint', function () { const timer = new timing.Timer(); timer._startTime = 12345; - expect(() => timer.getDuration()) - .to.throw('Unable to get duration'); + expect(() => timer.getDuration()).to.throw('Unable to get duration'); }); }); }); diff --git a/packages/support/test/unit/util.spec.js b/packages/support/test/unit/util.spec.js index db5c265fe..12f2fdae7 100644 --- a/packages/support/test/unit/util.spec.js +++ b/packages/support/test/unit/util.spec.js @@ -1,15 +1,12 @@ - -import { util, fs, tempDir } from '../../lib'; +import {util, fs, tempDir} from '../../lib'; import B from 'bluebird'; -import { createSandbox } from 'sinon'; +import {createSandbox} from 'sinon'; import os from 'os'; import path from 'path'; import _ from 'lodash'; const {W3C_WEB_ELEMENT_IDENTIFIER} = util; - - describe('util', function () { let sandbox; @@ -128,61 +125,58 @@ describe('util', function () { describe('localIp', function () { it('should find a local ip address', function () { let ifConfigOut = { - lo0: - [ - { - address: '::1', - netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', - family: 'IPv6', - mac: '00:00:00:00:00:00', - scopeid: 0, - internal: true, - }, - { - address: '127.0.0.1', - netmask: '255.0.0.0', - family: 'IPv4', - mac: '00:00:00:00:00:00', - internal: true, - }, - { - address: 'fe80::1', - netmask: 'ffff:ffff:ffff:ffff::', - family: 'IPv6', - mac: '00:00:00:00:00:00', - scopeid: 1, - internal: true, - } - ], - en0: - [ - { - address: 'xxx', - netmask: 'ffff:ffff:ffff:ffff::', - family: 'IPv6', - mac: 'd0:e1:40:93:56:9a', - scopeid: 4, - internal: false, - }, - { - address: '123.123.123.123', - netmask: '255.255.254.0', - family: 'IPv4', - mac: 'xxx', - internal: false, - } - ], - awdl0: - [ - { - address: 'xxx', - netmask: 'ffff:ffff:ffff:ffff::', - family: 'IPv6', - mac: 'xxx', - scopeid: 7, - internal: false, - } - ], + lo0: [ + { + address: '::1', + netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + family: 'IPv6', + mac: '00:00:00:00:00:00', + scopeid: 0, + internal: true, + }, + { + address: '127.0.0.1', + netmask: '255.0.0.0', + family: 'IPv4', + mac: '00:00:00:00:00:00', + internal: true, + }, + { + address: 'fe80::1', + netmask: 'ffff:ffff:ffff:ffff::', + family: 'IPv6', + mac: '00:00:00:00:00:00', + scopeid: 1, + internal: true, + }, + ], + en0: [ + { + address: 'xxx', + netmask: 'ffff:ffff:ffff:ffff::', + family: 'IPv6', + mac: 'd0:e1:40:93:56:9a', + scopeid: 4, + internal: false, + }, + { + address: '123.123.123.123', + netmask: '255.255.254.0', + family: 'IPv4', + mac: 'xxx', + internal: false, + }, + ], + awdl0: [ + { + address: 'xxx', + netmask: 'ffff:ffff:ffff:ffff::', + family: 'IPv6', + mac: 'xxx', + scopeid: 7, + internal: false, + }, + ], }; let osMock = sandbox.mock(os); osMock.expects('networkInterfaces').returns(ifConfigOut); @@ -264,7 +258,7 @@ describe('util', function () { k2: 'v2', k3: 'v3', }; - function replacer (key, value) { + function replacer(key, value) { return _.isString(value) ? value.toUpperCase() : value; } const jsonString = util.jsonStringify(obj, replacer); @@ -278,7 +272,7 @@ describe('util', function () { k2: 'v2', k3: Buffer.from('hi how are you today'), }; - function replacer (key, value) { + function replacer(key, value) { return _.isString(value) ? value.toUpperCase() : value; } const jsonString = util.jsonStringify(obj, replacer); @@ -295,7 +289,7 @@ describe('util', function () { k5: 'v5', }, }; - function replacer (key, value) { + function replacer(key, value) { return _.isString(value) ? value.toUpperCase() : value; } const jsonString = util.jsonStringify(obj, replacer); @@ -321,7 +315,7 @@ describe('util', function () { }); it('should unwrap a wrapped element that uses W3C element identifier', function () { let el = { - [W3C_WEB_ELEMENT_IDENTIFIER]: 5 + [W3C_WEB_ELEMENT_IDENTIFIER]: 5, }; util.unwrapElement(el).should.eql(5); }); @@ -408,10 +402,12 @@ describe('util', function () { b: 'b', c: 'c', }; - util.filterObject(obj, (v) => v === 'a' || v === 'c').should.eql({ - a: 'a', - c: 'c', - }); + util + .filterObject(obj, (v) => v === 'a' || v === 'c') + .should.eql({ + a: 'a', + c: 'c', + }); }); }); }); @@ -450,8 +446,12 @@ describe('util', function () { await fs.rimraf(tmpDir); }); it('should match paths to the same file/folder', async function () { - (await util.isSameDestination(path1, path.resolve(tmpDir, '..', path.basename(tmpDir), path.basename(path1)))) - .should.be.true; + ( + await util.isSameDestination( + path1, + path.resolve(tmpDir, '..', path.basename(tmpDir), path.basename(path1)) + ) + ).should.be.true; }); it('should not match paths if they point to non existing items', async function () { (await util.isSameDestination(path1, 'blabla')).should.be.false; @@ -481,7 +481,7 @@ describe('util', function () { describe('quote', function () { it('should quote a string with a space', function () { - util.quote(['a', 'b', 'c d']).should.eql('a b \'c d\''); + util.quote(['a', 'b', 'c d']).should.eql("a b 'c d'"); }); it('should escape double quotes', function () { util.quote(['a', 'b', `it's a "neat thing"`]).should.eql(`a b "it's a \\"neat thing\\""`); diff --git a/packages/test-support/lib/env-utils.js b/packages/test-support/lib/env-utils.js index 9c1956a1a..f6ae6e874 100644 --- a/packages/test-support/lib/env-utils.js +++ b/packages/test-support/lib/env-utils.js @@ -1,13 +1,14 @@ import _ from 'lodash'; -function stubEnv () { +function stubEnv() { let envBackup; - beforeEach(function beforeEach () { - envBackup = process.env; process.env = _.cloneDeep(process.env); + beforeEach(function beforeEach() { + envBackup = process.env; + process.env = _.cloneDeep(process.env); }); - afterEach(function afterEach () { + afterEach(function afterEach() { process.env = envBackup; }); } -export { stubEnv }; +export {stubEnv}; diff --git a/packages/test-support/lib/index.js b/packages/test-support/lib/index.js index b0bb4fa20..7fb92ad80 100644 --- a/packages/test-support/lib/index.js +++ b/packages/test-support/lib/index.js @@ -1,12 +1,10 @@ -import { stubEnv } from './env-utils'; -import { stubLog } from './log-utils'; -import { fakeTime } from './time-utils'; -import { withMocks, verifyMocks } from './mock-utils'; -import { withSandbox, verifySandbox } from './sandbox-utils'; +import {stubEnv} from './env-utils'; +import {stubLog} from './log-utils'; +import {fakeTime} from './time-utils'; +import {withMocks, verifyMocks} from './mock-utils'; +import {withSandbox, verifySandbox} from './sandbox-utils'; // this just needs to be imported, for the functionality to be injected import './unhandled-rejection'; -export { - stubEnv, stubLog, fakeTime, withSandbox, verifySandbox, withMocks, verifyMocks -}; +export {stubEnv, stubLog, fakeTime, withSandbox, verifySandbox, withMocks, verifyMocks}; diff --git a/packages/test-support/lib/log-utils.js b/packages/test-support/lib/log-utils.js index 18cdd4b45..dc6e1ab9f 100644 --- a/packages/test-support/lib/log-utils.js +++ b/packages/test-support/lib/log-utils.js @@ -1,16 +1,16 @@ // TODO move that to @appium/support -function stripColors (msg) { +function stripColors(msg) { let code = /\u001b\[(\d+(;\d+)*)?m/g; // eslint-disable-line no-control-regex msg = ('' + msg).replace(code, ''); return msg; } class LogStub { - constructor (opts = {}) { + constructor(opts = {}) { this.output = ''; this.stripColors = opts.stripColors; } - log (level, message) { + log(level, message) { if (this.stripColors) { message = stripColors(message); } @@ -21,14 +21,14 @@ class LogStub { } } -function stubLog (sandbox, log, opts = {}) { +function stubLog(sandbox, log, opts = {}) { let logStub = new LogStub(opts); for (let l of log.levels) { - sandbox.stub(log, l).callsFake(function doLogging (mess) { + sandbox.stub(log, l).callsFake(function doLogging(mess) { logStub.log(l, mess); }); } return logStub; } -export { stubLog }; +export {stubLog}; diff --git a/packages/test-support/lib/logger.js b/packages/test-support/lib/logger.js index 4c191e468..d404ee7fc 100644 --- a/packages/test-support/lib/logger.js +++ b/packages/test-support/lib/logger.js @@ -1,5 +1,4 @@ -import { logger } from '@appium/support'; - +import {logger} from '@appium/support'; const log = logger.getLogger('AppiumTestSupport'); diff --git a/packages/test-support/lib/mock-utils.js b/packages/test-support/lib/mock-utils.js index 88dd23bce..6dcdcdb43 100644 --- a/packages/test-support/lib/mock-utils.js +++ b/packages/test-support/lib/mock-utils.js @@ -3,34 +3,34 @@ import _ from 'lodash'; let SANDBOX = Symbol(); -function withMocks (libs, fn) { +function withMocks(libs, fn) { return () => { const mocks = { - verify () { + verify() { this.sandbox.verify(); }, - get sandbox () { + get sandbox() { return this[SANDBOX]; }, - set sandbox (sandbox) { + set sandbox(sandbox) { this[SANDBOX] = sandbox; }, }; - beforeEach(function beforeEach () { + beforeEach(function beforeEach() { mocks[SANDBOX] = sinon.createSandbox(); for (let [key, value] of _.toPairs(libs)) { mocks[key] = mocks.sandbox.mock(value); } }); - afterEach(function afterEach () { + afterEach(function afterEach() { mocks.sandbox.restore(); }); fn(mocks); }; } -function verifyMocks (mocks) { +function verifyMocks(mocks) { mocks.sandbox.verify(); } -export { withMocks, verifyMocks }; +export {withMocks, verifyMocks}; diff --git a/packages/test-support/lib/sandbox-utils.js b/packages/test-support/lib/sandbox-utils.js index 62ae97fec..66e302f9b 100644 --- a/packages/test-support/lib/sandbox-utils.js +++ b/packages/test-support/lib/sandbox-utils.js @@ -5,15 +5,15 @@ import B from 'bluebird'; let SANDBOX = Symbol(); // use this one if using a mix of mocks/stub/spies -function withSandbox (config, fn) { +function withSandbox(config, fn) { return () => { const S = { mocks: {}, - verify () { + verify() { return this.sandbox.verify(); }, }; - beforeEach(function beforeEach () { + beforeEach(function beforeEach() { S.sandbox = sinon.createSandbox(); S.sandbox.usingPromise(B); S.mocks[SANDBOX] = S.sandbox; @@ -21,7 +21,7 @@ function withSandbox (config, fn) { S.mocks[key] = S.sandbox.mock(value); } }); - afterEach(function afterEach () { + afterEach(function afterEach() { S.sandbox.restore(); for (let k of _.keys(S.mocks)) { delete S.mocks[k]; @@ -32,9 +32,9 @@ function withSandbox (config, fn) { }; } -function verifySandbox (obj) { +function verifySandbox(obj) { let sandbox = obj.sandbox ? obj.sandbox : obj[SANDBOX]; sandbox.verify(); } -export { withSandbox, verifySandbox }; +export {withSandbox, verifySandbox}; diff --git a/packages/test-support/lib/time-utils.js b/packages/test-support/lib/time-utils.js index 48344a8e1..8b8569636 100644 --- a/packages/test-support/lib/time-utils.js +++ b/packages/test-support/lib/time-utils.js @@ -1,14 +1,14 @@ -function fakeTime (sandbox) { +function fakeTime(sandbox) { let clock = sandbox.useFakeTimers(); return new TimeLord(clock); } class TimeLord { - constructor (clock) { + constructor(clock) { this.clock = clock; } - speedup (interval, times) { + speedup(interval, times) { let tick = (n) => { if (n === 0) return; // eslint-disable-line curly process.nextTick(() => { @@ -20,4 +20,4 @@ class TimeLord { tick(times); } } -export { fakeTime }; +export {fakeTime}; diff --git a/packages/test-support/lib/unhandled-rejection.js b/packages/test-support/lib/unhandled-rejection.js index 5f97850d1..bb938aa63 100644 --- a/packages/test-support/lib/unhandled-rejection.js +++ b/packages/test-support/lib/unhandled-rejection.js @@ -1,6 +1,5 @@ import loudRejection from 'loud-rejection'; - // in a testing environment (environment variable is set in `appium-gulp-utils`) // make sure unhandled promise rejections are made visible if (process.env._TESTING) { diff --git a/packages/test-support/test/unit/env-utils.spec.js b/packages/test-support/test/unit/env-utils.spec.js index 9fc43beec..95b2e17c8 100644 --- a/packages/test-support/test/unit/env-utils.spec.js +++ b/packages/test-support/test/unit/env-utils.spec.js @@ -1,5 +1,4 @@ -import { stubEnv } from '../../lib'; - +import {stubEnv} from '../../lib'; const expect = chai.expect; diff --git a/packages/test-support/test/unit/log-utils.spec.js b/packages/test-support/test/unit/log-utils.spec.js index 722a417c9..030f9702a 100644 --- a/packages/test-support/test/unit/log-utils.spec.js +++ b/packages/test-support/test/unit/log-utils.spec.js @@ -1,9 +1,8 @@ -import { stubLog } from '../../lib'; +import {stubLog} from '../../lib'; import log from '../../lib/logger'; import sinon from 'sinon'; import '@colors/colors'; - describe('log-utils', function () { describe('stubLog', function () { let sandbox; @@ -17,19 +16,15 @@ describe('log-utils', function () { let logStub = stubLog(sandbox, log); log.info('Hello World!'); log.warn(`The ${'sun'.yellow} is shining!`); - logStub.output.should.equals([ - 'info: Hello World!', - `warn: The ${'sun'.yellow} is shining!` - ].join('\n')); + logStub.output.should.equals( + ['info: Hello World!', `warn: The ${'sun'.yellow} is shining!`].join('\n') + ); }); it('should stub log and strip colors', function () { let logStub = stubLog(sandbox, log, {stripColors: true}); log.info('Hello World!'); log.warn(`The ${'sun'.yellow} is shining!`); - logStub.output.should.equals([ - 'info: Hello World!', - 'warn: The sun is shining!' - ].join('\n')); + logStub.output.should.equals(['info: Hello World!', 'warn: The sun is shining!'].join('\n')); }); }); }); diff --git a/packages/test-support/test/unit/sandbox-utils.spec.js b/packages/test-support/test/unit/sandbox-utils.spec.js index 982241c87..fb621072a 100644 --- a/packages/test-support/test/unit/sandbox-utils.spec.js +++ b/packages/test-support/test/unit/sandbox-utils.spec.js @@ -1,43 +1,43 @@ -import { withSandbox, verifySandbox } from '../../lib'; - +import {withSandbox, verifySandbox} from '../../lib'; const expect = chai.expect; let funcs = { - abc: () => 'abc' + abc: () => 'abc', }; describe('sandbox-utils', function () { - describe('withSandbox', withSandbox({mocks: {funcs}}, (S) => { - it('should create a sandbox and mocks', function () { - expect(S.sandbox).to.exist; - expect(S.mocks.funcs).to.exist; - funcs.abc().should.equal('abc'); - S.mocks.funcs.expects('abc').once().returns('efg'); - funcs.abc().should.equal('efg'); - S.sandbox.verify(); - }); + describe( + 'withSandbox', + withSandbox({mocks: {funcs}}, (S) => { + it('should create a sandbox and mocks', function () { + expect(S.sandbox).to.exist; + expect(S.mocks.funcs).to.exist; + funcs.abc().should.equal('abc'); + S.mocks.funcs.expects('abc').once().returns('efg'); + funcs.abc().should.equal('efg'); + S.sandbox.verify(); + }); - it('should be back to normal', function () { - funcs.abc().should.equal('abc'); - }); + it('should be back to normal', function () { + funcs.abc().should.equal('abc'); + }); - it('S.verify', function () { - expect(S.sandbox).to.exist; - expect(S.mocks.funcs).to.exist; - S.mocks.funcs.expects('abc').once().returns('efg'); - funcs.abc().should.equal('efg'); - S.verify(); - }); + it('S.verify', function () { + expect(S.sandbox).to.exist; + expect(S.mocks.funcs).to.exist; + S.mocks.funcs.expects('abc').once().returns('efg'); + funcs.abc().should.equal('efg'); + S.verify(); + }); - it('verifySandbox', function () { - expect(S.sandbox).to.exist; - expect(S.mocks.funcs).to.exist; - S.mocks.funcs.expects('abc').once().returns('efg'); - funcs.abc().should.equal('efg'); - verifySandbox(S); - }); - - - })); + it('verifySandbox', function () { + expect(S.sandbox).to.exist; + expect(S.mocks.funcs).to.exist; + S.mocks.funcs.expects('abc').once().returns('efg'); + funcs.abc().should.equal('efg'); + verifySandbox(S); + }); + }) + ); }); diff --git a/packages/test-support/test/unit/time-utils.spec.js b/packages/test-support/test/unit/time-utils.spec.js index 532f24c28..4520bfaba 100644 --- a/packages/test-support/test/unit/time-utils.spec.js +++ b/packages/test-support/test/unit/time-utils.spec.js @@ -1,13 +1,12 @@ -import { fakeTime } from '../../lib'; +import {fakeTime} from '../../lib'; import sinon from 'sinon'; import B from 'bluebird'; - -function doSomething () { +function doSomething() { return new B.Promise((resolve) => { let ret = ''; - function appendOneByOne () { + function appendOneByOne() { if (ret.length >= 10) { return resolve(ret); } diff --git a/packages/types/lib/capabilities.ts b/packages/types/lib/capabilities.ts index 5b9543374..b8f31143f 100644 --- a/packages/types/lib/capabilities.ts +++ b/packages/types/lib/capabilities.ts @@ -1,4 +1,4 @@ -import type { Capabilities as WdioCaps } from '@wdio/types'; +import type {Capabilities as WdioCaps} from '@wdio/types'; /** * The mask of a W3C-style namespaced capability proped name. @@ -19,31 +19,31 @@ type StringRecord = Record; * All known capabilities derived from wdio's {@linkcode WdioCaps.Capabilities Capabilities} type, accepting additional optional caps. * All properties are optional. */ -type BaseCapabilities = - Partial; +type BaseCapabilities = Partial< + WdioCaps.Capabilities & OptionalCaps +>; /** * All known capabilities derived from wdio's {@linkcode WdioCaps.Capabilities Capabilities} type and wdio's {@linkcode WdioCaps.AppiumCapabilities} type, accepting additional optional caps. - * + * * In practice, the properties `platformName` and `automationName` are required by Appium. */ -export type Capabilities = - BaseCapabilities; +export type Capabilities = BaseCapabilities< + WdioCaps.AppiumCapabilities & OptionalCaps +>; /** * All known capabilities derived from wdio's {@linkcode WdioCaps.Capabilities Capabilities} type and wdio's {@linkcode WdioCaps.AppiumW3CCapabilities} type, accepting additional optional _namespaced_ caps. */ export type AppiumW3CCapabilities< - OptionalNamespacedCaps extends NamespacedRecord = NamespacedRecord, + OptionalNamespacedCaps extends NamespacedRecord = NamespacedRecord > = BaseCapabilities; /** * All known capabilities derived from wdio's {@linkcode WdioCaps.Capabilities Capabilities} type and wdio's {@linkcode WdioCaps.AppiumW3Capabilities} type, accepting additional optional _namespaced_ caps, in W3C-compatible format (`alwaysMatch`/`firstMatch`). * In practice, the properties `appium:platformName` and `appium:automationName` are required by Appium _somewhere_ in this object; this cannot be expressed in TypeScript. */ -export type W3CCapabilities< - OptionalNamespacedCaps extends NamespacedRecord = NamespacedRecord, -> = { +export type W3CCapabilities = { alwaysMatch: AppiumW3CCapabilities; firstMatch: AppiumW3CCapabilities[]; }; @@ -55,6 +55,5 @@ export type AppiumAndroidCapabilities = WdioCaps.AppiumAndroidCapabilities; export type AppiumIOSCapabilities = WdioCaps.AppiumIOSCapabilities; export type AppiumXCUICommandTimeouts = WdioCaps.AppiumXCUICommandTimeouts; export type AppiumXCUIProcessArguments = WdioCaps.AppiumXCUIProcessArguments; -export type AppiumXCUISafariGlobalPreferences = - WdioCaps.AppiumXCUISafariGlobalPreferences; +export type AppiumXCUISafariGlobalPreferences = WdioCaps.AppiumXCUISafariGlobalPreferences; export type AppiumXCUITestCapabilities = WdioCaps.AppiumXCUITestCapabilities; diff --git a/packages/types/lib/config.ts b/packages/types/lib/config.ts index 0d8a50424..a35f10b96 100644 --- a/packages/types/lib/config.ts +++ b/packages/types/lib/config.ts @@ -1,5 +1,5 @@ -import type { AppiumConfigJsonSchema } from '@appium/schema'; -import { AppiumConfiguration, ServerConfig } from './appium-config'; +import type {AppiumConfigJsonSchema} from '@appium/schema'; +import {AppiumConfiguration, ServerConfig} from './appium-config'; /** * The Appium configuration as it would be in a user-provided configuration file. @@ -9,8 +9,7 @@ export type AppiumConfig = Partial; /** * Derive the "constant" type of the server properties from the schema. */ - type AppiumServerJsonSchema = - typeof AppiumConfigJsonSchema['properties']['server']['properties']; +type AppiumServerJsonSchema = typeof AppiumConfigJsonSchema['properties']['server']['properties']; /** * This type associates the types generated from the schema ({@linkcode AppiumConfiguration}) @@ -67,35 +66,34 @@ export type ServerArgs = { /** * Converts a kebab-cased string into a camel-cased string. */ - type KebabToCamel = - S extends `${infer P1}-${infer P2}${infer P3}` - ? `${Lowercase}${Uppercase}${KebabToCamel}` - : Lowercase; +type KebabToCamel = S extends `${infer P1}-${infer P2}${infer P3}` + ? `${Lowercase}${Uppercase}${KebabToCamel}` + : Lowercase; /** -* Converts an object with kebab-cased keys into camel-cased keys. -*/ -type ObjectToCamel = { - [K in keyof T as KebabToCamel]: T[K] extends Record - ? KeysToCamelCase - : T[K]; -}; - -/** -* Converts an object or array to have camel-cased keys. -*/ -type KeysToCamelCase = { - [K in keyof T as KebabToCamel]: T[K] extends Array - ? KeysToCamelCase[] - : ObjectToCamel; -}; - -/** - * Object `B` has all the keys as object `A` (even if those keys in `A` are otherwise optional). + * Converts an object with kebab-cased keys into camel-cased keys. */ -type Associated]: unknown }> = { +type ObjectToCamel = { + [K in keyof T as KebabToCamel]: T[K] extends Record + ? KeysToCamelCase + : T[K]; +}; + +/** + * Converts an object or array to have camel-cased keys. + */ +type KeysToCamelCase = { + [K in keyof T as KebabToCamel]: T[K] extends Array + ? KeysToCamelCase[] + : ObjectToCamel; +}; + +/** + * Object `B` has all the keys as object `A` (even if those keys in `A` are otherwise optional). + */ +type Associated]: unknown}> = { [Prop in keyof Required]: B[Prop]; -} +}; // end utils @@ -109,7 +107,7 @@ type Associated]: unknow * * See `appium/lib/schema/keywords` for definition of `appiumCliDest`. */ - interface WithDest { +interface WithDest { appiumCliDest: string; } @@ -125,4 +123,3 @@ interface WithDefault { } // end conditionals - diff --git a/packages/types/lib/index.ts b/packages/types/lib/index.ts index f9396ffdb..b25877c5d 100644 --- a/packages/types/lib/index.ts +++ b/packages/types/lib/index.ts @@ -1,15 +1,15 @@ -import type { Method as _Method } from 'axios'; -import type { EventEmitter } from 'events'; -import type { Server } from 'http'; -import type { Logger } from 'npmlog'; -import type { Class as _Class, MultidimensionalReadonlyArray } from 'type-fest'; -import { ServerArgs } from './config'; -import { Capabilities, W3CCapabilities } from './capabilities'; +import type {Method as _Method} from 'axios'; +import type {EventEmitter} from 'events'; +import type {Server} from 'http'; +import type {Logger} from 'npmlog'; +import type {Class as _Class, MultidimensionalReadonlyArray} from 'type-fest'; +import {ServerArgs} from './config'; +import {Capabilities, W3CCapabilities} from './capabilities'; -export { AppiumW3CCapabilities } from './capabilities'; -export { AppiumConfig, NormalizedAppiumConfig } from './config'; +export {AppiumW3CCapabilities} from './capabilities'; +export {AppiumConfig, NormalizedAppiumConfig} from './config'; export * from './appium-config'; -export { ServerArgs, Capabilities, W3CCapabilities }; +export {ServerArgs, Capabilities, W3CCapabilities}; /** * Methods and properties which both `AppiumDriver` and `BaseDriver` inherit. @@ -72,12 +72,7 @@ export interface Driver startNewCommandTimeout(): Promise; reset(): Promise; - assignServer?( - server: AppiumServer, - host: string, - port: number, - path: string, - ): void; + assignServer?(server: AppiumServer, host: string, port: number, path: string): void; } /** @@ -104,12 +99,7 @@ export interface ExternalDriver extends Driver { getWindowHandles?(): Promise; setFrame?(id: null | number | string): Promise; getWindowRect?(): Promise; - setWindowRect?( - x: number, - y: number, - width: number, - height: number, - ): Promise; + setWindowRect?(x: number, y: number, width: number, height: number): Promise; maximizeWindow?(): Promise; minimizeWindow?(): Promise; fullScreenWindow?(): Promise; @@ -155,18 +145,10 @@ export interface ExternalDriver extends Driver { getPerformanceData?( packageName: string, dataType: string, - dataReadTimeout?: number, + dataReadTimeout?: number ): Promise; - pressKeyCode?( - keycode: number, - metastate?: number, - flags?: number, - ): Promise; - longPressKeyCode?( - keycode: number, - metastate?: number, - flags?: number, - ): Promise; + pressKeyCode?(keycode: number, metastate?: number, flags?: number): Promise; + longPressKeyCode?(keycode: number, metastate?: number, flags?: number): Promise; fingerprint?(fingerprintId: number): Promise; sendSMS?(phoneNumber: string, message: string): Promise; gsmCall?(phoneNumber: string, action: string): Promise; @@ -183,7 +165,7 @@ export interface ExternalDriver extends Driver { rotation: number, touchCount: number, duration: string, - elementId?: string, + elementId?: string ): Promise; getCurrentActivity?(): Promise; getCurrentPackage?(): Promise; @@ -193,12 +175,7 @@ export interface ExternalDriver extends Driver { terminateApp?(appId: string, options?: unknown): Promise; isAppInstalled?(appId: string): Promise; queryAppState?(appId: string): Promise; - hideKeyboard?( - strategy?: string, - key?: string, - keyCode?: string, - keyName?: string, - ): Promise; + hideKeyboard?(strategy?: string, key?: string, keyCode?: string, keyName?: string): Promise; isKeyboardShown?(): Promise; pushFile?(path: string, data: string): Promise; pullFile?(path: string): Promise; @@ -217,7 +194,7 @@ export interface ExternalDriver extends Driver { intentCategory?: string, intentFlags?: string, optionalIntentArguments?: string, - dontStopAppOnReset?: boolean, + dontStopAppOnReset?: boolean ): Promise; getSystemBars?(): Promise; getDisplayDensity?(): Promise; @@ -227,18 +204,11 @@ export interface ExternalDriver extends Driver { closeApp?(): Promise; background?(seconds: null | number): Promise; endCoverage?(intent: string, path: string): Promise; - getStrings?( - language?: string, - stringFile?: string, - ): Promise>; + getStrings?(language?: string, stringFile?: string): Promise>; setValueImmediate?(value: string, elementId: string): Promise; replaceValue?(value: string, elementId: string): Promise; receiveAsyncResponse?(response: unknown): Promise; - setClipboard?( - content: string, - contentType?: string, - label?: string, - ): Promise; + setClipboard?(content: string, contentType?: string, label?: string): Promise; getClipboard?(contentType?: string): Promise; // JSONWP @@ -268,11 +238,7 @@ export interface ExternalDriver extends Driver { activateIMEEngine?(engine: string): Promise; getOrientation?(): Promise; setOrientation?(orientation: string): Promise; - moveTo?( - element?: null | string, - xOffset?: number, - yOffset?: number, - ): Promise; + moveTo?(element?: null | string, xOffset?: number, yOffset?: number): Promise; buttonDown?(button?: number): Promise; buttonUp?(button?: number): Promise; clickCurrent?(button?: number): Promise; @@ -287,7 +253,7 @@ export interface ExternalDriver extends Driver { ySpeed?: number, xOffset?: number, yOffset?: number, - speed?: number, + speed?: number ): Promise; getGeoLocation?(): Promise; setGeoLocation?(location: Partial): Promise; @@ -314,7 +280,7 @@ export interface ExternalDriver extends Driver { hasResidentKey?: boolean, hasUserVerification?: boolean, isUserConsenting?: boolean, - isUserVerified?: boolean, + isUserVerified?: boolean ): Promise; removeVirtualAuthenticator?(): Promise; addAuthCredential?( @@ -323,18 +289,14 @@ export interface ExternalDriver extends Driver { rpId: string, privateKey: string, userHandle?: string, - signCount?: number, + signCount?: number ): Promise; getAuthCredential?(): Promise; removeAllAuthCredentials?(): Promise; removeAuthCredential?(): Promise; setUserAuthVerified?(isUserVerified: boolean): Promise; - proxyCommand?( - url: string, - method: HTTPMethod, - body?: string, - ): Promise; + proxyCommand?(url: string, method: HTTPMethod, body?: string): Promise; } export interface Method { @@ -352,7 +314,10 @@ export interface PayloadParams { makeArgs?: (obj: any) => any; } -export type MethodMap = Record>>; +export type MethodMap = Record< + string, + Record> +>; export interface Constraint { presence?: boolean | {allowEmpty: boolean}; @@ -371,23 +336,20 @@ export interface Element { } export interface DriverHelpers { - configureApp: ( - app: string, - supportedAppExtensions: string[], - ) => Promise; + configureApp: (app: string, supportedAppExtensions: string[]) => Promise; isPackageOrBundle: (app: string) => boolean; duplicateKeys: (input: T, firstKey: string, secondKey: string) => T; parseCapsArray: (cap: string | string[]) => string[]; generateDriverLogPrefix: (obj: Core, sessionId?: string) => string; } -export type SettingsUpdateListener< - T extends Record = Record, -> = (prop: keyof T, newValue: unknown, curValue: unknown) => Promise; +export type SettingsUpdateListener = Record> = ( + prop: keyof T, + newValue: unknown, + curValue: unknown +) => Promise; -export interface DeviceSettings< - T extends Record = Record, -> { +export interface DeviceSettings = Record> { update(newSettings: T): Promise; getSettings(): T; } @@ -502,13 +464,13 @@ export interface EventHistoryCommand { } export type HTTPMethod = _Method; -export type Prefix = string|(() => string); +export type Prefix = string | (() => string); export interface AppiumLogger { unwrap(): Logger; level: string; levels: string[]; - prefix?: Prefix, + prefix?: Prefix; debug: (...args: any[]) => void; info: (...args: any[]) => void; warn: (...args: any[]) => void; @@ -529,7 +491,7 @@ export interface TimeoutCommands { ms: number | string, script?: number, pageLoad?: number, - implicit?: number | string, + implicit?: number | string ): Promise; setNewCommandTimeout(ms: number): void; implicitWait(ms: number | string): Promise; @@ -566,29 +528,25 @@ export type SingularSessionData = Capabilities & {events?: EventHistory}; export interface FindCommands { findElement(strategy: string, selector: string): Promise; findElements(strategy: string, selector: string): Promise; - findElementFromElement( - strategy: string, - selector: string, - elementId: string, - ): Promise; + findElementFromElement(strategy: string, selector: string, elementId: string): Promise; findElementsFromElement( strategy: string, selector: string, - elementId: string, + elementId: string ): Promise; findElOrEls( strategy: string, selector: string, mult: Mult, - context?: string, + context?: string ): Promise; findElOrElsWithProcessing( strategy: string, selector: string, mult: Mult, - context?: string, + context?: string ): Promise; getPageSource(): Promise; @@ -599,7 +557,7 @@ export interface LogCommands { getLogTypes(): Promise; /** * Gets logs - * + * * TODO: `logType` should be a key in `supportedLogTypes`, and the return value of this function * should be the associated `LogType` object's `LogEntry` parameterized type. * @param logType - Name/key of log type as defined in {@linkcode LogCommands.supportedLogTypes}. @@ -617,13 +575,10 @@ export interface SessionHandler { w3cCaps1: W3CCapabilities, w3cCaps2?: W3CCapabilities, w3cCaps?: W3CCapabilities, - driverData?: DriverData[], + driverData?: DriverData[] ): Promise; - deleteSession( - sessionId?: string, - driverData?: DriverData[], - ): Promise; + deleteSession(sessionId?: string, driverData?: DriverData[]): Promise; } export type DriverData = Record; @@ -634,5 +589,5 @@ export type DriverData = Record; export type Class< Proto, StaticMembers extends object = {}, - Args extends unknown[] = any[], + Args extends unknown[] = any[] > = _Class & StaticMembers; diff --git a/packages/types/scripts/generate-schema-types.js b/packages/types/scripts/generate-schema-types.js index bac1b15a1..eaee47f7b 100644 --- a/packages/types/scripts/generate-schema-types.js +++ b/packages/types/scripts/generate-schema-types.js @@ -32,19 +32,17 @@ const JSON_SCHEMA_PATH = path.join(SCHEMA_ROOT, 'lib', 'appium-config.schema.jso * * This is a `.ts` file, _not_ a `.d.ts`; this is so it will be output as a `.d.ts` into `build` by `tsc`. */ -const OUTPUT_PATH = path.join( - TYPES_ROOT, - 'lib', - 'appium-config.ts' -); +const OUTPUT_PATH = path.join(TYPES_ROOT, 'lib', 'appium-config.ts'); -async function main () { +async function main() { try { let ts; try { ts = await compileFromFile(JSON_SCHEMA_PATH); } catch (err) { - throw new Error(`${error} Could not convert Appium schema JSON to TypeScript: ${err.message}. Does it exist?`); + throw new Error( + `${error} Could not convert Appium schema JSON to TypeScript: ${err.message}. Does it exist?` + ); } try { await fs.writeFile(OUTPUT_PATH, ts); diff --git a/packages/universal-xml-plugin/lib/attr-map.js b/packages/universal-xml-plugin/lib/attr-map.js index b98646284..cb32621ff 100644 --- a/packages/universal-xml-plugin/lib/attr-map.js +++ b/packages/universal-xml-plugin/lib/attr-map.js @@ -32,4 +32,4 @@ const REMOVE_ATTRS = [ 'rotation', ]; -export { ATTR_MAP, REMOVE_ATTRS }; +export {ATTR_MAP, REMOVE_ATTRS}; diff --git a/packages/universal-xml-plugin/lib/index.js b/packages/universal-xml-plugin/lib/index.js index 8d0a8fe31..7326a50ea 100755 --- a/packages/universal-xml-plugin/lib/index.js +++ b/packages/universal-xml-plugin/lib/index.js @@ -1,11 +1,11 @@ import UniversalXMLPlugin from './plugin'; export default UniversalXMLPlugin; -export { UniversalXMLPlugin }; +export {UniversalXMLPlugin}; -export function main () { - const { transformSourceXml } = require('./source'); +export function main() { + const {transformSourceXml} = require('./source'); const fs = require('fs'); - const [,, xmlDataPath, platform, optsJson] = process.argv; + const [, , xmlDataPath, platform, optsJson] = process.argv; const xmlData = fs.readFileSync(xmlDataPath, 'utf8'); let opts = {}; if (optsJson) { diff --git a/packages/universal-xml-plugin/lib/logger.js b/packages/universal-xml-plugin/lib/logger.js index 0f09c90bc..a46e80441 100644 --- a/packages/universal-xml-plugin/lib/logger.js +++ b/packages/universal-xml-plugin/lib/logger.js @@ -1,3 +1,3 @@ -import { logger } from '@appium/support'; +import {logger} from '@appium/support'; const log = logger.getLogger('UniversalXMLPlugin'); export default log; diff --git a/packages/universal-xml-plugin/lib/node-map.js b/packages/universal-xml-plugin/lib/node-map.js index 87a5a95f9..26cf5d70e 100644 --- a/packages/universal-xml-plugin/lib/node-map.js +++ b/packages/universal-xml-plugin/lib/node-map.js @@ -64,23 +64,14 @@ export default { 'XCUIElementTypeStatusItem', 'XCUIElementTypeTimeline', ], - android: [ - 'android.widget.Space', - 'android.widget.TwoLineListItem', - ], + android: ['android.widget.Space', 'android.widget.TwoLineListItem'], }, Grid: { ios: 'XCUIElementTypeGrid', - android: [ - 'android.widget.GridLayout', - 'android.widget.GridView', - ], + android: ['android.widget.GridLayout', 'android.widget.GridView'], }, Icon: { - ios: [ - 'XCUIElementTypeIcon', - 'XCUIElementTypeDockItem', - ], + ios: ['XCUIElementTypeIcon', 'XCUIElementTypeDockItem'], }, Image: { ios: 'XCUIElementTypeImage', @@ -94,17 +85,11 @@ export default { 'XCUIElementTypeRelevanceIndicator', 'XCUIElementTypeValueIndicator', ], - android: [ - 'android.widget.RatingBar', - 'android.widget.ProgressBar', - ], + android: ['android.widget.RatingBar', 'android.widget.ProgressBar'], }, Input: { - ios: [ - 'XCUIElementTypeColorWell', - ], - android: [ - ] + ios: ['XCUIElementTypeColorWell'], + android: [], }, List: { android: [ @@ -112,18 +97,13 @@ export default { 'android.widget.ExpandableListView', 'android.widget.Gallery', ], - ios: [ - 'XCUIElementTypeCollectionView' - ], + ios: ['XCUIElementTypeCollectionView'], }, Map: { ios: 'XCUIElementTypeMap', }, Menu: { - ios: [ - 'XCUIElementTypeMenu', - 'XCUIElementTypeMenuBar', - ], + ios: ['XCUIElementTypeMenu', 'XCUIElementTypeMenuBar'], android: ['android.widget.ActionMenuView', 'android.widget.PopupMenu'], }, Modal: { @@ -133,11 +113,7 @@ export default { 'android.widget.SlidingDrawer', 'android.widget.Magnifier', ], - ios: [ - 'XCUIElementTypeDrawer', - 'XCUIElementTypeDialog', - 'XCUIElementTypePopover', - ], + ios: ['XCUIElementTypeDrawer', 'XCUIElementTypeDialog', 'XCUIElementTypePopover'], }, Nav: { ios: 'XCUIElementTypeNavigationBar', @@ -173,11 +149,7 @@ export default { }, SliderInput: { android: 'android.widget.SeekBar', - ios: [ - 'XCUIElementTypeSlider', - 'XCUIElementTypeStepper', - 'XCUIElementTypeScrollBar', - ], + ios: ['XCUIElementTypeSlider', 'XCUIElementTypeStepper', 'XCUIElementTypeScrollBar'], }, Spinner: { ios: 'XCUIElementTypeActivityIndicator', @@ -192,23 +164,11 @@ export default { android: 'android.widget.TableLayout', }, Text: { - ios: [ - 'XCUIElementTypeStaticText', - 'XCUIElementTypeTextView', - 'XCUIElementTypeHelpTag', - ], - android: [ - 'android.widget.TextView', - 'android.widget.Chronometer', - 'android.widget.TextClock', - ], + ios: ['XCUIElementTypeStaticText', 'XCUIElementTypeTextView', 'XCUIElementTypeHelpTag'], + android: ['android.widget.TextView', 'android.widget.Chronometer', 'android.widget.TextClock'], }, TextInput: { - ios: [ - 'XCUIElementTypeTextField', - 'XCUIElementTypeSecureTextField', - 'XCUIElementTypeComboBox', - ], + ios: ['XCUIElementTypeTextField', 'XCUIElementTypeSecureTextField', 'XCUIElementTypeComboBox'], android: [ 'android.widget.EditText', 'android.widget.AutoCompleteTextView', @@ -217,11 +177,7 @@ export default { }, ToggleInput: { ios: 'XCUIElementTypeToggle', - android: [ - 'android.widget.CheckedTextView', - 'android.widget.ToggleButton', - ], - + android: ['android.widget.CheckedTextView', 'android.widget.ToggleButton'], }, Toolbar: { ios: 'XCUIElementTypeToolbar', diff --git a/packages/universal-xml-plugin/lib/plugin.js b/packages/universal-xml-plugin/lib/plugin.js index bb3750d41..6f28ae625 100644 --- a/packages/universal-xml-plugin/lib/plugin.js +++ b/packages/universal-xml-plugin/lib/plugin.js @@ -1,49 +1,59 @@ /* eslint-disable no-case-declarations */ import BasePlugin from '@appium/base-plugin'; -import { errors } from '@appium/base-driver'; -import { transformSourceXml } from './source'; -import { transformQuery } from './xpath'; +import {errors} from '@appium/base-driver'; +import {transformSourceXml} from './source'; +import {transformQuery} from './xpath'; import log from './logger'; export default class UniversalXMLPlugin extends BasePlugin { + commands = [ + 'getPageSource', + 'findElement', + 'findElements', + 'findElementFromElement', + 'findElementsFromElement', + ]; - commands = ['getPageSource', 'findElement', 'findElements', 'findElementFromElement', - 'findElementsFromElement']; - - async getPageSource (next, driver, sessId, addIndexPath = false) { + async getPageSource(next, driver, sessId, addIndexPath = false) { const source = next ? await next() : await driver.getPageSource(); const metadata = {}; const {platformName} = driver.caps; if (platformName.toLowerCase() === 'android') { metadata.appPackage = driver.opts.appPackage; } - const {xml, unknowns} = transformSourceXml(source, - platformName.toLowerCase(), {metadata, addIndexPath}); + const {xml, unknowns} = transformSourceXml(source, platformName.toLowerCase(), { + metadata, + addIndexPath, + }); if (unknowns.nodes.length) { - log.warn(`The XML mapper found ${unknowns.nodes.length} node(s) / ` + - `tag name(s) that it didn't know about. These should be ` + - `reported to improve the quality of the plugin: ` + - unknowns.nodes.join(', ')); + log.warn( + `The XML mapper found ${unknowns.nodes.length} node(s) / ` + + `tag name(s) that it didn't know about. These should be ` + + `reported to improve the quality of the plugin: ` + + unknowns.nodes.join(', ') + ); } if (unknowns.attrs.length) { - log.warn(`The XML mapper found ${unknowns.attrs.length} attributes ` + - `that it didn't know about. These should be reported to ` + - `improve the quality of the plugin: ` + - unknowns.attrs.join(', ')); + log.warn( + `The XML mapper found ${unknowns.attrs.length} attributes ` + + `that it didn't know about. These should be reported to ` + + `improve the quality of the plugin: ` + + unknowns.attrs.join(', ') + ); } return xml; } - async findElement (...args) { + async findElement(...args) { return await this._find(false, ...args); } - async findElements (...args) { + async findElements(...args) { return await this._find(true, ...args); } - async _find (multiple, next, driver, strategy, selector) { + async _find(multiple, next, driver, strategy, selector) { const {platformName} = driver.caps; if (strategy.toLowerCase() !== 'xpath') { return await next(); @@ -54,8 +64,10 @@ export default class UniversalXMLPlugin extends BasePlugin { // if the selector was not able to be transformed, that means no elements were found that // matched, so do the appropriate thing based on element vs elements if (newSelector === null) { - log.warn(`Selector was not able to be translated to underlying XML. Either the requested ` + - `element does not exist or there was an error in translation`); + log.warn( + `Selector was not able to be translated to underlying XML. Either the requested ` + + `element does not exist or there was an error in translation` + ); if (multiple) { return []; } @@ -74,7 +86,6 @@ export default class UniversalXMLPlugin extends BasePlugin { const finder = multiple ? 'findElements' : 'findElement'; return await driver[finder](strategy, newSelector); } - } -export { UniversalXMLPlugin }; +export {UniversalXMLPlugin}; diff --git a/packages/universal-xml-plugin/lib/source.js b/packages/universal-xml-plugin/lib/source.js index 7b8835c3e..70e5bb799 100644 --- a/packages/universal-xml-plugin/lib/source.js +++ b/packages/universal-xml-plugin/lib/source.js @@ -1,7 +1,7 @@ import _ from 'lodash'; -import parser, { j2xParser } from 'fast-xml-parser'; +import parser, {j2xParser} from 'fast-xml-parser'; import NODE_MAP from './node-map'; -import { ATTR_MAP, REMOVE_ATTRS } from './attr-map'; +import {ATTR_MAP, REMOVE_ATTRS} from './attr-map'; import TRANSFORMS from './transformers'; const PARSE_OPTS = { @@ -22,19 +22,23 @@ export const IDX_PREFIX = `${ATTR_PREFIX}index`; const isAttr = (k) => k.substring(0, 2) === ATTR_PREFIX; const isNode = (k) => !isAttr(k); -export function transformSourceXml (xmlStr, platform, {metadata = {}, addIndexPath = false} = {}) { +export function transformSourceXml(xmlStr, platform, {metadata = {}, addIndexPath = false} = {}) { // first thing we want to do is modify the ios source root node, because it doesn't include the // necessary index attribute, so we add it if it's not there xmlStr = xmlStr.replace('', ''); const xmlObj = parser.parse(xmlStr, PARSE_OPTS); - const unknowns = transformNode(xmlObj, platform, {metadata, addIndexPath, parentPath: ''}); + const unknowns = transformNode(xmlObj, platform, { + metadata, + addIndexPath, + parentPath: '', + }); const jParser = new j2xParser(GEN_OPTS); let transformedXml = jParser.parse(xmlObj).trim(); transformedXml = `\n${transformedXml}`; return {xml: transformedXml, unknowns}; } -function getUniversalName (nameMap, name, platform) { +function getUniversalName(nameMap, name, platform) { for (const translatedName of Object.keys(nameMap)) { const sourceNodes = nameMap[translatedName]?.[platform]; if (_.isArray(sourceNodes) && sourceNodes.includes(name)) { @@ -47,15 +51,15 @@ function getUniversalName (nameMap, name, platform) { return null; } -export function getUniversalNodeName (nodeName, platform) { +export function getUniversalNodeName(nodeName, platform) { return getUniversalName(NODE_MAP, nodeName, platform); } -export function getUniversalAttrName (attrName, platform) { +export function getUniversalAttrName(attrName, platform) { return getUniversalName(ATTR_MAP, attrName, platform); } -export function transformNode (nodeObj, platform, {metadata, addIndexPath, parentPath}) { +export function transformNode(nodeObj, platform, {metadata, addIndexPath, parentPath}) { const unknownNodes = []; const unknownAttrs = []; if (_.isPlainObject(nodeObj)) { @@ -76,14 +80,18 @@ export function transformNode (nodeObj, platform, {metadata, addIndexPath, paren TRANSFORMS[platform]?.(nodeObj, metadata); unknownAttrs.push(...transformAttrs(nodeObj, attrs, platform)); const unknowns = transformChildNodes(nodeObj, childNodeNames, platform, { - metadata, addIndexPath, parentPath: thisIndexPath + metadata, + addIndexPath, + parentPath: thisIndexPath, }); unknownAttrs.push(...unknowns.attrs); unknownNodes.push(...unknowns.nodes); } else if (_.isArray(nodeObj)) { for (const childObj of nodeObj) { const {nodes, attrs} = transformNode(childObj, platform, { - metadata, addIndexPath, parentPath + metadata, + addIndexPath, + parentPath, }); unknownNodes.push(...nodes); unknownAttrs.push(...attrs); @@ -95,12 +103,21 @@ export function transformNode (nodeObj, platform, {metadata, addIndexPath, paren }; } -export function transformChildNodes (nodeObj, childNodeNames, platform, {metadata, addIndexPath, parentPath}) { +export function transformChildNodes( + nodeObj, + childNodeNames, + platform, + {metadata, addIndexPath, parentPath} +) { const unknownNodes = []; const unknownAttrs = []; for (const nodeName of childNodeNames) { // before modifying the name of this child node, recurse down and modify the subtree - const {nodes, attrs} = transformNode(nodeObj[nodeName], platform, {metadata, addIndexPath, parentPath}); + const {nodes, attrs} = transformNode(nodeObj[nodeName], platform, { + metadata, + addIndexPath, + parentPath, + }); unknownNodes.push(...nodes); unknownAttrs.push(...attrs); @@ -127,7 +144,7 @@ export function transformChildNodes (nodeObj, childNodeNames, platform, {metadat return {nodes: unknownNodes, attrs: unknownAttrs}; } -export function transformAttrs (nodeObj, attrs, platform) { +export function transformAttrs(nodeObj, attrs, platform) { const unknownAttrs = []; for (const attr of attrs) { const cleanAttr = attr.substring(2); diff --git a/packages/universal-xml-plugin/lib/transformers.js b/packages/universal-xml-plugin/lib/transformers.js index 5da995f72..0941c56a6 100644 --- a/packages/universal-xml-plugin/lib/transformers.js +++ b/packages/universal-xml-plugin/lib/transformers.js @@ -1,10 +1,10 @@ -import { ATTR_PREFIX } from './source'; +import {ATTR_PREFIX} from './source'; -function ios (nodeObj/*, metadata*/) { +function ios(nodeObj /*, metadata*/) { return nodeObj; } -function android (nodeObj, metadata) { +function android(nodeObj, metadata) { // strip android:id from front of id const resId = nodeObj[`${ATTR_PREFIX}resource-id`]; if (resId && metadata.appPackage) { diff --git a/packages/universal-xml-plugin/lib/xpath.js b/packages/universal-xml-plugin/lib/xpath.js index fd13f307e..2b762e7e9 100644 --- a/packages/universal-xml-plugin/lib/xpath.js +++ b/packages/universal-xml-plugin/lib/xpath.js @@ -1,13 +1,13 @@ -import { select as xpathQuery } from 'xpath'; -import { DOMParser } from 'xmldom'; +import {select as xpathQuery} from 'xpath'; +import {DOMParser} from 'xmldom'; -export function runQuery (query, xmlStr) { +export function runQuery(query, xmlStr) { const dom = new DOMParser().parseFromString(xmlStr); const nodes = xpathQuery(query, dom); return nodes; } -export function transformQuery (query, xmlStr, multiple) { +export function transformQuery(query, xmlStr, multiple) { const nodes = runQuery(query, xmlStr); const newQueries = nodes.map((node) => { @@ -16,7 +16,8 @@ export function transformQuery (query, xmlStr, multiple) { let newQuery = indexPath .substring(1) // remove leading / so we can split .split('/') // split into idnexes - .map((indexStr) => { // map to xpath node indexes (1-based) + .map((indexStr) => { + // map to xpath node indexes (1-based) const xpathIndex = parseInt(indexStr, 10) + 1; return `*[${xpathIndex}]`; }) @@ -37,7 +38,7 @@ export function transformQuery (query, xmlStr, multiple) { return newSelector; } -export function getNodeAttrVal (node, attr) { +export function getNodeAttrVal(node, attr) { const attrObjs = Object.values(node.attributes).filter((obj) => obj.name === attr); if (!attrObjs.length) { throw new Error(`Tried to retrieve a node attribute '${attr}' but the node didn't have it`); diff --git a/packages/universal-xml-plugin/test/unit/plugin.spec.js b/packages/universal-xml-plugin/test/unit/plugin.spec.js index 6681ad663..dfb343e40 100644 --- a/packages/universal-xml-plugin/test/unit/plugin.spec.js +++ b/packages/universal-xml-plugin/test/unit/plugin.spec.js @@ -1,7 +1,7 @@ import UniversalXMLPlugin from '../../lib/plugin'; import BaseDriver from '@appium/base-driver'; -import { XML_IOS, XML_ANDROID, XML_IOS_TRANSFORMED, XML_ANDROID_TRANSFORMED } from '../fixtures'; -import { runQuery, getNodeAttrVal } from '../../lib/xpath'; +import {XML_IOS, XML_ANDROID, XML_IOS_TRANSFORMED, XML_ANDROID_TRANSFORMED} from '../fixtures'; +import {runQuery, getNodeAttrVal} from '../../lib/xpath'; describe('UniversalXMLPlugin', function () { let next; diff --git a/packages/universal-xml-plugin/test/unit/source.spec.js b/packages/universal-xml-plugin/test/unit/source.spec.js index d0b58f8d8..084de1687 100644 --- a/packages/universal-xml-plugin/test/unit/source.spec.js +++ b/packages/universal-xml-plugin/test/unit/source.spec.js @@ -1,26 +1,40 @@ import chai from 'chai'; -import { XML_IOS, XML_IOS_TRANSFORMED, XML_IOS_TRANSFORMED_INDEX_PATH, - XML_IOS_EDGE, XML_IOS_EDGE_TRANSFORMED } from '../fixtures'; -import { transformAttrs, transformChildNodes, transformSourceXml } from '../../lib/source'; +import { + XML_IOS, + XML_IOS_TRANSFORMED, + XML_IOS_TRANSFORMED_INDEX_PATH, + XML_IOS_EDGE, + XML_IOS_EDGE_TRANSFORMED, +} from '../fixtures'; +import {transformAttrs, transformChildNodes, transformSourceXml} from '../../lib/source'; chai.should(); describe('source functions', function () { describe('transformSourceXml', function () { it('should transform an xml doc based on platform', function () { - const {xml, unknowns: {nodes, attrs}} = transformSourceXml(XML_IOS, 'ios'); + const { + xml, + unknowns: {nodes, attrs}, + } = transformSourceXml(XML_IOS, 'ios'); xml.should.eql(XML_IOS_TRANSFORMED); nodes.should.eql([]); attrs.should.eql([]); }); it('should transform an xml doc and include index path', function () { - const {xml, unknowns: {nodes, attrs}} = transformSourceXml(XML_IOS, 'ios', {addIndexPath: true}); + const { + xml, + unknowns: {nodes, attrs}, + } = transformSourceXml(XML_IOS, 'ios', {addIndexPath: true}); xml.should.eql(XML_IOS_TRANSFORMED_INDEX_PATH); nodes.should.eql([]); attrs.should.eql([]); }); it('should transform an xml doc and return any unknown nodes or attrs', function () { - const {xml, unknowns: {nodes, attrs}} = transformSourceXml(XML_IOS_EDGE, 'ios'); + const { + xml, + unknowns: {nodes, attrs}, + } = transformSourceXml(XML_IOS_EDGE, 'ios'); xml.should.eql(XML_IOS_EDGE_TRANSFORMED); nodes.should.eql(['SomeRandoElement']); attrs.should.eql(['oddAttribute']); @@ -28,22 +42,47 @@ describe('source functions', function () { }); describe('transformChildNodes', function () { it('should loop through child nodes of an object and transform them based on platform', function () { - const node = {XCUIElementTypeIcon: [{}], XCUIElementTypeKey: [{}], XCUIElementTypeTab: [{}]}; + const node = { + XCUIElementTypeIcon: [{}], + XCUIElementTypeKey: [{}], + XCUIElementTypeTab: [{}], + }; const metadata = {}; - transformChildNodes(node, Object.keys(node), 'ios', metadata).should.eql({nodes: [], attrs: []}); + transformChildNodes(node, Object.keys(node), 'ios', metadata).should.eql({ + nodes: [], + attrs: [], + }); node.should.eql({Button: [{}, {}], Icon: [{}]}); }); it('should leave unknown nodes intact and add them to unknowns list', function () { - const node = {XCUIElementTypeIcon: [{}], UnknownThingo: [{}], XCUIElementTypeTab: [{}]}; + const node = { + XCUIElementTypeIcon: [{}], + UnknownThingo: [{}], + XCUIElementTypeTab: [{}], + }; const metadata = {}; - transformChildNodes(node, Object.keys(node), 'ios', metadata).should.eql({nodes: ['UnknownThingo'], attrs: []}); + transformChildNodes(node, Object.keys(node), 'ios', metadata).should.eql({ + nodes: ['UnknownThingo'], + attrs: [], + }); node.should.eql({Button: [{}], UnknownThingo: [{}], Icon: [{}]}); }); it('should leave nodes for other platforms intact and add them to unknowns list', function () { - const node = {XCUIElementTypeIcon: [{}], 'android.widget.EditText': [{}], XCUIElementTypeTab: [{}]}; + const node = { + XCUIElementTypeIcon: [{}], + 'android.widget.EditText': [{}], + XCUIElementTypeTab: [{}], + }; const metadata = {}; - transformChildNodes(node, Object.keys(node), 'ios', metadata).should.eql({nodes: ['android.widget.EditText'], attrs: []}); - node.should.eql({Button: [{}], 'android.widget.EditText': [{}], Icon: [{}]}); + transformChildNodes(node, Object.keys(node), 'ios', metadata).should.eql({ + nodes: ['android.widget.EditText'], + attrs: [], + }); + node.should.eql({ + Button: [{}], + 'android.widget.EditText': [{}], + Icon: [{}], + }); }); }); describe('transformAttrs', function () { @@ -62,7 +101,11 @@ describe('source functions', function () { unknowns.should.eql([]); }); it('should not translate unknown attributes and return them in the unknowns list', function () { - const obj = {'@_type': 'foo', '@_resource-id': 'someId', '@_rando': 'lorian'}; + const obj = { + '@_type': 'foo', + '@_resource-id': 'someId', + '@_rando': 'lorian', + }; const attrs = Object.keys(obj); const unknowns = transformAttrs(obj, attrs, 'android'); obj.should.eql({'@_id': 'someId', '@_rando': 'lorian'}); diff --git a/packages/universal-xml-plugin/test/unit/xpath.spec.js b/packages/universal-xml-plugin/test/unit/xpath.spec.js index 075b0d029..080402bc8 100644 --- a/packages/universal-xml-plugin/test/unit/xpath.spec.js +++ b/packages/universal-xml-plugin/test/unit/xpath.spec.js @@ -1,7 +1,7 @@ import chai from 'chai'; -import { runQuery, transformQuery, getNodeAttrVal } from '../../lib/xpath'; -import { transformSourceXml } from '../../lib/source'; -import { XML_IOS } from '../fixtures'; +import {runQuery, transformQuery, getNodeAttrVal} from '../../lib/xpath'; +import {transformSourceXml} from '../../lib/source'; +import {XML_IOS} from '../fixtures'; const should = chai.should(); @@ -15,21 +15,20 @@ describe('xpath functions', function () { describe('transformQuery', function () { it('should transform a query into a single new query', function () { const {xml} = transformSourceXml(XML_IOS, 'ios', {addIndexPath: true}); - transformQuery('//TextInput', xml, false) - .should.eql('/*[1]/*[1]/*[1]/*[1]/*[2]/*[1]/*[1]/*[1]/*[1]/*[1]/*[1]/*[2]/*[1]/*[1]/*[1]'); + transformQuery('//TextInput', xml, false).should.eql( + '/*[1]/*[1]/*[1]/*[1]/*[2]/*[1]/*[1]/*[1]/*[1]/*[1]/*[1]/*[2]/*[1]/*[1]/*[1]' + ); }); it('should transform a query into a multiple new queries if asked', function () { const {xml} = transformSourceXml(XML_IOS, 'ios', {addIndexPath: true}); - transformQuery('//Window', xml, true) - .split('|') - .should.have.length(2); + transformQuery('//Window', xml, true).split('|').should.have.length(2); }); it('should return null for queries that dont find anything', function () { const {xml} = transformSourceXml(XML_IOS, 'ios', {addIndexPath: true}); should.not.exist(transformQuery('//blah', xml, false)); }); }); - describe ('getNodeAttrVal', function () { + describe('getNodeAttrVal', function () { it('should get the attribute for a node', function () { const node = runQuery('//XCUIElementTypeTextField', XML_IOS)[0]; getNodeAttrVal(node, 'name').should.eql('username');