mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 09:34:19 +08:00
chore(e2e): remove old e2e testing
This commit is contained in:
@ -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)
|
|
@ -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
|
|
||||||
};
|
|
@ -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))));
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
@ -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
|
|
||||||
}
|
|
Reference in New Issue
Block a user