mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 17:42:15 +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