diff --git a/core/scripts/e2e/README.md b/core/scripts/e2e/README.md deleted file mode 100644 index 1e95394a16..0000000000 --- a/core/scripts/e2e/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# End-To-End Testing Scripts - -This document describes the process of installing the dependencies for, running, and writing end-to-end tests for ionic core. Your working directory is assumed to be `core`. - ---- - -## Dependencies - -Before you proceed, make sure you're running [**Node 7.6.0+**](https://nodejs.org/en/download/): - -```sh -node -v # Should be >= 7.6.0 -``` - -And that you've installed all packages: - -```sh -npm install -``` - -## Running The Tests - -To run the end-to-end tests: - -``` -npm run e2e -``` - -## Writing an End-To-End Test - -To create an end-to-end test, you'll need to create a directory containing your test page and the tests themselves. Create a directory in your component's `test` directory. That directory should contain an `index.html` and an `e2e.js` file. So, if I were writing a test called `basic` for the `button` component: - -``` -button -└── test/ - └── basic/ - ├── e2e.js - └── index.html -``` - -In your `e2e.js` file, you can group tests together using [Mocha](https://mochajs.org/)'s `describe` function: - -```js -describe('button: basic', () => { - // Write tests here. -}); -``` - -To register a test, use the `register` method from `scripts/e2e`. The `register` function takes two arguments, a description and a callback. The callback is passed the test [driver](https://www.npmjs.com/package/selenium-webdriver) as its only argument. For async actions, simply return a Promise from your callback. - -```js -const { register } = require('../../../../../scripts/e2e'); - -describe('button: basic', () => { - register('my test', driver => { - // Use the driver here. - }); -}); -``` - -The most basic, and most common, test simply navigates to your page to ensure it renders properly. For more complicated cases, you may need to extend the `Page` class provided by the e2e module. - -### Simple Navigation Tests - -To write a simple navigation test, you can use the `navigate` function from the e2e module: - -```js -const { register, navigate } = require('../../../../../scripts/e2e'); - -describe('button: basic', () => { - register('navigates', navigate('http://localhost:3333/src/components/button/test/basic')); -}); -``` - -### Extending The `Page` Class - -For more complicated tests, you may need to extend the `Page` class: - -```js -const { register, Page } = require('../../../../scripts/e2e');; - -class ButtonTestPage extends Page { - constructor(driver) { - super(driver, 'http://localhost:3333/src/components/button/test/basic'); - } - - someMethod() { - // ... - } -} - -describe('button: basic', () => { - register('some test', driver => { - const page = new ButtonTestPage(driver); - return page.someMethod(); - }); -}); -``` - -## Snapshot - -You can also take snapshots of each end-to-end test to check for visual regressions. You'll need to export the `IONIC_SNAPSHOT_KEY` environment variable to upload to the snapshot app. Ask someone from Ionic for the key. - -**Snapshot compares a base snapshot made on MacOS with a retina screen. (2560x1600) It does not work for Windows, Linux, or non-retina Macs.** - -To take snapshots: - -```bash -npm run snapshot -``` - -To take snapshots of a specific folder: - -```bash -npm run snapshot -- -f=toast -``` - -## TODO - -- [ ] Move this script up a directory and use for all packages? -- [ ] Turn off animations and then adjust the wait time accordingly -- [ ] Adjustments will likely be needed when the Snapshot tool has better reporting, for example the tool will likely have `start` and `finish` methods (or some such thing) diff --git a/core/scripts/e2e/index.js b/core/scripts/e2e/index.js deleted file mode 100644 index 95b77101c2..0000000000 --- a/core/scripts/e2e/index.js +++ /dev/null @@ -1,205 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const glob = require('glob'); -const Mocha = require('mocha'); -const path = require('path'); -const webdriver = require('selenium-webdriver'); -const argv = require('yargs').argv; - -const Page = require('./page'); -const Snapshot = require('./snapshot'); - -const platforms = ['md', 'ios']; - -let driver; -let snapshot; -let specIndex = 0; -let takeScreenshots = false; -let folder = null; - -function startDevServer() { - const server = require('@stencil/dev-server/dist'); // TODO: fix after stencil-dev-server PR #16 is merged - const cmdArgs = [ - '--config', - path.join(__dirname, '../../stencil.config.js'), - '--no-open' - ]; - - return server.run(cmdArgs); -} - -function generateTestId() { - let chars = 'abcdefghjkmnpqrstuvwxyz'; - let id = chars.charAt(Math.floor(Math.random() * chars.length)); - chars += '0123456789'; - while (id.length < 3) { - id += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return id; -} - -function getTestFiles() { - return new Promise((resolve, reject) => { - let src = path.join(__dirname, '../../src/**/e2e.js'); - - if (folder) { - src = path.join(__dirname, `../../src/**/${folder}/**/e2e.js`); - } - - glob(src, (err, files) => { - if (err) { - reject(err); - } else { - resolve(files); - } - }); - }); -} - -function startDriver() { - // setting chrome options to start the browser without an info bar - let chromeCapabilities = webdriver.Capabilities.chrome(); - const chromeOptions = { - // 'args': ['--disable-infobars'] - }; - chromeCapabilities.set('chromeOptions', chromeOptions); - - return new webdriver.Builder() - .withCapabilities(chromeCapabilities) - .forBrowser('chrome') - .build(); -} - -function processCommandLine() { - if (argv.snapshot) { - takeScreenshots = true; - } - - if (argv.f || argv.folder) { - folder = argv.f || argv.folder; - } -} - -function registerE2ETest(desc, tst) { - // NOTE: Do not use an arrow function here because: https://mochajs.org/#arrow-functions - it(desc, async function() { - await tst(driver, this); - if (takeScreenshots) { - await snapshot.takeScreenshot(driver, { - name: this.test.fullTitle().replace(/(^[\w-]+\/[\w-]+)/, '$1:'), - specIndex: specIndex++ - }); - } - }); -} - -function getTotalTests(suite) { - let ttl = suite.tests.length; - suite.suites.forEach(s => (ttl += getTotalTests(s))); - return ttl; -} - -async function run() { - // TODO look into removing chrome startup from the timeout - const mocha = new Mocha({ - timeout: 5000, - slow: 2000 - }); - - driver = startDriver(); - processCommandLine(); - - const devServer = await startDevServer(); - - const files = await getTestFiles(); - files.forEach(f => mocha.addFile(f)); - snapshot = await mochaLoadFiles(mocha); - const failures = await mochaRun(mocha); - - if (takeScreenshots) { - snapshot.finish(); - } - - devServer.close(); - await driver.quit(); - - if (failures) { - throw new Error(failures); - } -} - -function mochaRun(mocha) { - return new Promise((resolve, reject) => { - mocha.run(function(failures) { - resolve(failures); - }); - }); -} - -function mochaLoadFiles(mocha) { - return new Promise((resolve, reject) => { - mocha.loadFiles(() => { - specIndex = 0; - - snapshot = new Snapshot({ - groupId: 'ionic-core', - appId: 'snapshots', - testId: generateTestId(), - domain: 'ionic-snapshot-go.appspot.com', - // domain: 'localhost:8080', - sleepBetweenSpecs: 750, - totalSpecs: getTotalTests(mocha.suite), - platformDefaults: { - browser: 'chrome', - platform: 'linux', - params: { - platform_id: 'chrome_400x800', - platform_index: 0, - platform_count: 1, - width: 400, - height: 814 - } - }, - accessKey: process.env.IONIC_SNAPSHOT_KEY - }); - resolve(snapshot); - }); - }); -} - -function parseSemver(str) { - return /(\d+)\.(\d+)\.(\d+)/ - .exec(str) - .slice(1) - .map(Number); -} - -function validateNodeVersion(version) { - const [major, minor] = parseSemver(version); - - if (major < 7 || (major === 7 && minor < 6)) { - throw new Error( - 'Running the end-to-end tests requires Node version 7.6.0 or higher.' - ); - } -} - -// Invoke run() only if executed directly i.e. `node ./scripts/e2e` -if (require.main === module) { - validateNodeVersion(process.version); - run() - .then(() => {}) - .catch(err => { - console.log(err); - // fail with non-zero status code - process.exit(1); - }); -} - -module.exports = { - Page, - platforms: platforms, - register: registerE2ETest, - run: run -}; diff --git a/core/scripts/e2e/page.js b/core/scripts/e2e/page.js deleted file mode 100644 index bcc3220e45..0000000000 --- a/core/scripts/e2e/page.js +++ /dev/null @@ -1,25 +0,0 @@ -const webdriver = require('selenium-webdriver'); -const By = webdriver.By; -const until = webdriver.until; - -module.exports = class E2ETestPage { - constructor(driver, url) { - this.url = url; - this.driver = driver; - } - - async navigate(tagName = '') { - this.driver.navigate().to(this.url); - this.driver.manage().timeouts().implicitlyWait(10000); - await this.driver.wait(until.elementLocated(By.css(`.hydrated`))); - const tag = tagName || '.hydrated'; - return await this.driver.wait(until.elementIsVisible(this.driver.findElement(By.css(tag)))); - } - - async present(clickTarget, options) { - await this.navigate(clickTarget); - this.driver.findElement(By.css(clickTarget)).click(); - await this.driver.wait(until.elementLocated(By.css(options.waitFor))); - return await this.driver.wait(until.elementIsVisible(this.driver.findElement(By.css(options.waitFor)))); - } -} diff --git a/core/scripts/e2e/snapshot.js b/core/scripts/e2e/snapshot.js deleted file mode 100644 index 38c69343bc..0000000000 --- a/core/scripts/e2e/snapshot.js +++ /dev/null @@ -1,137 +0,0 @@ -'use strict'; - -const request = require('request'); - -class Snapshot { - constructor(options) { - this.appId = (options && options.appId) || 'test_app'; - this.domain = (options && options.domain) || 'localhost:8080'; - this.groupId = (options && options.groupId) || 'test_group'; - this.sleepTime = (options && options.sleepBetweenSpecs) || 500; - this.totalSpecs = options && options.totalSpecs; - this.accessKey = options && options.accessKey; - this.platformId = - options && options.platformDefaults && options.platformDefaults.params && options.platformDefaults.params.platform_id; - this.platformIndex = - options && options.platformDefaults && options.platformDefaults.params && options.platformDefaults.params.platform_index; - this.platformCount = - options && options.platformDefaults && options.platformDefaults.params && options.platformDefaults.params.platform_count; - this.width = - (options && options.platformDefaults && options.platformDefaults.params && options.platformDefaults.params.width) || -1; - this.height = - (options && options.platformDefaults && options.platformDefaults.params && options.platformDefaults.params.height) || -1; - - this.testId = (options && options.testId); - this.queue = []; - this.highestMismatch = 0; - this.mismatches = []; - this.results = {}; - } - - async finish() { - console.log('waiting for uploads to complete'); - await Promise.all(this.queue); - console.log(`done processing ${this.queue.length} screenshots`); - console.log(`${this.mismatches.length} snapshots had significant mismatches`); - console.log(`Test Id: ${this.testId}`); - console.log(`URL: https://${this.domain}/${this.groupId}/${this.appId}/${this.testId}`); - } - - async takeScreenshot(driver, options) { - this._resizeWindow(driver); - await this._allowForAnnimation(); - const screenshot = await this._takeScreenshot(driver, options); - return this._post(screenshot); - } - - _allowForAnnimation() { - return new Promise((resolve, reject) => { - setTimeout(function() { - resolve(); - }, this.sleepTime); - }); - } - - _post(screenshot) { - const p = new Promise((resolve, reject) => { - request.post(`http://${this.domain}/screenshot`, { form: screenshot }, (error, res, body) => { - if (error) { - console.error(error); - } else if (res.statusCode > 400) { - console.log('error posting screenshot:', response.statusCode, body); - } else { - const data = JSON.parse(body); - this.highestMismatch = Math.max(this.highestMismatch, data.Mismatch); - const resultKey = (data.Mismatch * 1000 + 1000000 + '').split('.')[0] + '-' + screenshot.spec_index; - this.results[resultKey] = { - index: screenshot.spec_index, - name: screenshot.description, - mismatch: Math.round(data.Mismatch * 100) / 100, - compareUrl: data.CompareUrl, - screenshotUrl: data.ScreenshotUrl - }; - if (data.IsMismatch) { - this.mismatches.push(resultKey); - } - } - resolve(res); - }); - }); - this.queue.push(p); - return Promise.resolve(true); - } - - _resizeWindow(driver) { - return driver - .manage() - .window() - .setSize(this.width, this.height); - } - - _getQueryString(field, url) { - var reg = new RegExp( '[?&]' + field + '=([^&#]*)', 'i' ); - var string = reg.exec(url); - return string ? string[1] : null; - }; - - async _takeScreenshot(driver, options) { - const capabilities = await driver.getCapabilities(); - - const png = await driver.takeScreenshot(); - let url = await driver.getCurrentUrl(); - - // TODO remove the modified url/description once we're happy with the comparison to v3 - let platform = 'android'; - - if (url.indexOf('ionic:mode') > -1) { - platform = this._getQueryString('ionic:mode', url); - } - - let replacedUrl = url.replace('3333', '8876').replace('src/components', '/e2e').replace('test/', '').replace(`?ionic:mode=${platform}`, ''); - url = replacedUrl + `/index.html?ionic:mode=${platform}&ionicOverlayCreatedDiff=0&snapshot=true`; - - let description = options.name.replace(': ', `: ${platform} `) + '.'; - - return Promise.resolve({ - app_id: this.appId, - group_id: this.groupId, - description: description, - spec_index: options.specIndex, - total_specs: this.totalSpecs, - test_id: this.testId, - url: url, - png_base64: png, - height: this.height, - width: this.width, - platform_count: this.platformCount, - platform_id: this.platformId, - platform_index: this.platformIndex, - browser: capabilities.get('browserName'), - platform: capabilities.get('platform'), - version: capabilities.get('version'), - access_key: this.accessKey - }); - } -} - -module.exports = Snapshot; diff --git a/core/scripts/e2e/utils.js b/core/scripts/e2e/utils.js deleted file mode 100644 index ee50dc8c1d..0000000000 --- a/core/scripts/e2e/utils.js +++ /dev/null @@ -1,27 +0,0 @@ -const { By, until } = require('selenium-webdriver'); - -async function getElement(driver, selector) { - driver.wait(until.elementLocated(By.css(selector))); - const element = driver.findElement(By.css(selector)); - await driver.wait(until.elementIsVisible(driver.findElement(By.css(selector)))); - return element; -} - -async function waitAndGetElementById(driver, selector) { - driver.wait(until.elementLocated(By.id(selector))); - const element = driver.findElement(By.id(selector)); - await driver.wait(until.elementIsVisible(driver.findElement(By.id(selector)))); - return element; -} - -function waitForTransition(duration) { - return new Promise(resolve => { - setTimeout(resolve, duration); - }) -} - -module.exports = { - getElement: getElement, - waitForTransition: waitForTransition, - waitAndGetElementById: waitAndGetElementById -}