@enact/ui-test-utils 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/wdio.conf.js +2 -2
- package/package.json +2 -3
- package/screenshot/utils/confHelpers.js +17 -23
- package/screenshot/wdio.conf.js +6 -7
- package/ui/wdio.docker.conf.js +1 -1
- package/utils/Page.js +23 -19
- package/utils/runTest.js +24 -10
- package/utils/selector.js +4 -4
package/config/wdio.conf.js
CHANGED
|
@@ -23,7 +23,7 @@ module.exports.configure = (options) => {
|
|
|
23
23
|
try {
|
|
24
24
|
if (process.platform === 'win32') {
|
|
25
25
|
// Windows
|
|
26
|
-
const chromeVersion = /\d+/.exec(execSync('wmic datafile where "name=\'C:\\\\Program Files
|
|
26
|
+
const chromeVersion = /\d+/.exec(execSync('wmic datafile where "name=\'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\'" get Version /value').toString());
|
|
27
27
|
chromeVersionMajorNumber = (chromeVersion && chromeVersion[0]);
|
|
28
28
|
} else if (process.platform === 'darwin') {
|
|
29
29
|
// Mac
|
|
@@ -107,7 +107,7 @@ module.exports.configure = (options) => {
|
|
|
107
107
|
//
|
|
108
108
|
browserName: 'chrome',
|
|
109
109
|
'goog:chromeOptions': visibleBrowser ? {} : {
|
|
110
|
-
args: ['--headless', '--window-size=1920,
|
|
110
|
+
args: ['--headless', '--window-size=1920,1080']
|
|
111
111
|
}
|
|
112
112
|
}],
|
|
113
113
|
//
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enact/ui-test-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "UI Testing for the Enact framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
"@wdio/selenium-standalone-service": "^7.8.0",
|
|
24
24
|
"@wdio/spec-reporter": "^7.9.0",
|
|
25
25
|
"@wdio/static-server-service": "^7.8.0",
|
|
26
|
-
"@wdio/sync": "^7.9.0",
|
|
27
26
|
"chai": "4.3.4",
|
|
28
27
|
"chalk": "^4.1.2",
|
|
29
28
|
"cross-spawn": "^7.0.3",
|
|
@@ -35,7 +34,7 @@
|
|
|
35
34
|
"ramda": "^0.27.1",
|
|
36
35
|
"readdirp": "^3.6.0",
|
|
37
36
|
"wdio-docker-service": "^3.1.2",
|
|
38
|
-
"wdio-
|
|
37
|
+
"wdio-image-comparison-service": "^3.0.0",
|
|
39
38
|
"webdriverio": "^7.9.0"
|
|
40
39
|
},
|
|
41
40
|
"devDependencies": {
|
|
@@ -2,7 +2,6 @@ const crypto = require('crypto'),
|
|
|
2
2
|
path = require('path'),
|
|
3
3
|
fs = require('fs');
|
|
4
4
|
|
|
5
|
-
const VisualRegressionCompare = require('wdio-novus-visual-regression-service/compare');
|
|
6
5
|
const buildApps = require('../../src/build-apps');
|
|
7
6
|
const makeHeader = require('./headerTemplate');
|
|
8
7
|
|
|
@@ -23,21 +22,18 @@ function getScreenshotName (basePath) {
|
|
|
23
22
|
|
|
24
23
|
// shorten the name with a little bit of leading context to help find the file manually if necessary
|
|
25
24
|
testName = testName.substring(0, 128) + '-' + crypto.createHash('md5').update(testName).digest('hex');
|
|
26
|
-
|
|
27
|
-
return
|
|
25
|
+
let screenshotFileName = path.join(basePath, ...testNameParts, `${testName}.png`);
|
|
26
|
+
return screenshotFileName.replace(/ /g, '_');
|
|
28
27
|
};
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const distPath = path.join(process.cwd(), 'tests', 'screenshot', 'dist');
|
|
31
|
+
const baselineRelativePath = 'screenshots/reference';
|
|
32
|
+
const screenshotRelativePath = 'screenshots/screen';
|
|
33
|
+
const baselineFolder = path.join(distPath, baselineRelativePath);
|
|
34
|
+
const screenshotFolder = path.join(distPath, screenshotRelativePath);
|
|
34
35
|
|
|
35
|
-
const
|
|
36
|
-
referenceName: generateReferenceName,
|
|
37
|
-
screenshotName: generateScreenshotName,
|
|
38
|
-
diffName: generateDiffName,
|
|
39
|
-
misMatchTolerance: 0.005
|
|
40
|
-
});
|
|
36
|
+
const generateReferenceName = getScreenshotName(baselineFolder);
|
|
41
37
|
|
|
42
38
|
function initFile (name, content) {
|
|
43
39
|
const dir = path.dirname(name);
|
|
@@ -77,16 +73,16 @@ function beforeTest (testData) {
|
|
|
77
73
|
function afterTest (testData, _context, {passed}) {
|
|
78
74
|
// If this doesn't include context data, not a screenshot test
|
|
79
75
|
if (testData && testData.title && testData.context && testData.context.params) {
|
|
76
|
+
const fileName = testData.context.fileName.replace(/ /g, '_') + '.png';
|
|
77
|
+
const referencePath = path.join(baselineRelativePath, fileName);
|
|
78
|
+
|
|
80
79
|
if (_context.isNewScreenshot) {
|
|
81
|
-
const filename = generateReferenceName({test: testData});
|
|
82
80
|
fs.open(newScreenshotFilename, 'a', (err, fd) => {
|
|
83
|
-
const distPath = path.join(process.cwd(), 'tests', 'screenshot', 'dist'),
|
|
84
|
-
relativeName = path.relative(distPath, filename);
|
|
85
81
|
if (err) {
|
|
86
82
|
console.error('Unable to create log file!');
|
|
87
83
|
} else {
|
|
88
84
|
const {params, url} = testData.context;
|
|
89
|
-
const output = {title: testData.title.replace(/~\//g, '/'), path:
|
|
85
|
+
const output = {title: testData.title.replace(/~\//g, '/'), path: referencePath, params, url};
|
|
90
86
|
fs.appendFile(fd, `${JSON.stringify(output)},`, 'utf8', () => {
|
|
91
87
|
fs.close(fd);
|
|
92
88
|
});
|
|
@@ -95,11 +91,9 @@ function afterTest (testData, _context, {passed}) {
|
|
|
95
91
|
}
|
|
96
92
|
|
|
97
93
|
if (!passed) {
|
|
94
|
+
const screenPath = path.join(screenshotRelativePath, 'actual', fileName);
|
|
95
|
+
const diffPath = path.join(screenshotRelativePath, 'diff', fileName);
|
|
98
96
|
fs.open(failedScreenshotFilename, 'a', (err, fd) => {
|
|
99
|
-
const distPath = path.join(process.cwd(), 'tests', 'screenshot', 'dist'),
|
|
100
|
-
diffPath = path.relative(distPath, generateDiffName({test: testData})),
|
|
101
|
-
referencePath = path.relative(distPath, generateReferenceName({test: testData})),
|
|
102
|
-
screenPath = path.relative(distPath, generateScreenshotName({test: testData}));
|
|
103
97
|
if (err) {
|
|
104
98
|
console.error('Unable to create failed test log file!');
|
|
105
99
|
} else {
|
|
@@ -115,7 +109,6 @@ function afterTest (testData, _context, {passed}) {
|
|
|
115
109
|
}
|
|
116
110
|
}
|
|
117
111
|
|
|
118
|
-
|
|
119
112
|
function onComplete () {
|
|
120
113
|
const {size: newSize} = fs.statSync(newScreenshotFilename),
|
|
121
114
|
{size: failedSize} = fs.statSync(failedScreenshotFilename);
|
|
@@ -141,8 +134,9 @@ function onComplete () {
|
|
|
141
134
|
|
|
142
135
|
module.exports = {
|
|
143
136
|
afterTest,
|
|
137
|
+
baselineFolder,
|
|
144
138
|
beforeTest,
|
|
145
|
-
comparator,
|
|
146
139
|
onComplete,
|
|
147
|
-
onPrepare
|
|
140
|
+
onPrepare,
|
|
141
|
+
screenshotFolder
|
|
148
142
|
};
|
package/screenshot/wdio.conf.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
// const visualRegression = require('wdio-novus-visual-regression-service');
|
|
2
1
|
const {configure} = require('../config/wdio.conf.js');
|
|
3
|
-
const {afterTest,
|
|
2
|
+
const {afterTest, baselineFolder, beforeTest, onComplete, onPrepare, screenshotFolder} = require('./utils/confHelpers.js');
|
|
4
3
|
|
|
5
4
|
exports.config = configure({
|
|
6
5
|
base: 'screenshot',
|
|
7
6
|
services: [[
|
|
8
|
-
'
|
|
7
|
+
'image-comparison',
|
|
9
8
|
{
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
baselineFolder: baselineFolder,
|
|
10
|
+
formatImageName: '{tag}',
|
|
11
|
+
screenshotPath: screenshotFolder,
|
|
12
|
+
autoSaveBaseline: true
|
|
14
13
|
}
|
|
15
14
|
]],
|
|
16
15
|
//
|
package/ui/wdio.docker.conf.js
CHANGED
|
@@ -3,7 +3,7 @@ const {config} = require('./wdio.conf.js');
|
|
|
3
3
|
|
|
4
4
|
// Remove selenium-standalone and replace with docker service
|
|
5
5
|
const services = config.services
|
|
6
|
-
.filter(service => service !== 'selenium-standalone')
|
|
6
|
+
.filter(service => service[0] !== 'selenium-standalone')
|
|
7
7
|
.concat(['docker']);
|
|
8
8
|
|
|
9
9
|
exports.config = Object.assign(
|
package/utils/Page.js
CHANGED
|
@@ -12,13 +12,17 @@ class Page {
|
|
|
12
12
|
return this._url;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
open (appPath, urlExtra = '?locale=en-US') {
|
|
15
|
+
async open (appPath, urlExtra = '?locale=en-US') {
|
|
16
16
|
this._url = `/${appPath}/${urlExtra}`;
|
|
17
17
|
// Can't resize browser window when connected to remote debugger!
|
|
18
18
|
if (!browser._options || !browser._options.remote) {
|
|
19
|
-
browser.setWindowSize(1920, 1080);
|
|
19
|
+
await browser.setWindowSize(1920, 1080);
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
await browser.url(this.url);
|
|
23
|
+
|
|
24
|
+
const body = await $('body');
|
|
25
|
+
await body.waitForExist({timeout: 1000});
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
serializeParams (params) {
|
|
@@ -27,14 +31,14 @@ class Page {
|
|
|
27
31
|
return queryObject;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
delay (delay = 1000) {
|
|
31
|
-
browser.pause(delay);
|
|
34
|
+
async delay (delay = 1000) {
|
|
35
|
+
await browser.pause(delay);
|
|
32
36
|
return browser;
|
|
33
37
|
}
|
|
34
|
-
keyDelay (key, delay = 50) {
|
|
35
|
-
browser.keys(key);
|
|
36
|
-
browser.pause(delay);
|
|
37
|
-
return browser;
|
|
38
|
+
async keyDelay (key, delay = 50) {
|
|
39
|
+
await browser.keys(key);
|
|
40
|
+
await browser.pause(delay);
|
|
41
|
+
return await browser;
|
|
38
42
|
}
|
|
39
43
|
spotlightLeft () {
|
|
40
44
|
return this.keyDelay('Left arrow');
|
|
@@ -64,25 +68,25 @@ class Page {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
// For testing "pointer off" by timeout.
|
|
67
|
-
hidePointerByKeycode () {
|
|
71
|
+
async hidePointerByKeycode () {
|
|
68
72
|
browser.execute(function () {
|
|
69
73
|
const event = document.createEvent('Events');
|
|
70
74
|
event.initEvent('keydown', true, true);
|
|
71
75
|
event.keyCode = 1537;
|
|
72
76
|
document.getElementById('root').dispatchEvent(event);
|
|
73
77
|
});
|
|
74
|
-
this.delay();
|
|
78
|
+
await this.delay();
|
|
75
79
|
return browser;
|
|
76
80
|
}
|
|
77
81
|
|
|
78
|
-
showPointerByKeycode () {
|
|
79
|
-
browser.execute(function () {
|
|
82
|
+
async showPointerByKeycode () {
|
|
83
|
+
await browser.execute(function () {
|
|
80
84
|
const event = document.createEvent('Events');
|
|
81
85
|
event.initEvent('keydown', true, true);
|
|
82
86
|
event.keyCode = 1536;
|
|
83
87
|
document.getElementById('root').dispatchEvent(event);
|
|
84
88
|
});
|
|
85
|
-
this.delay();
|
|
89
|
+
await this.delay();
|
|
86
90
|
return browser;
|
|
87
91
|
}
|
|
88
92
|
|
|
@@ -128,12 +132,12 @@ class Page {
|
|
|
128
132
|
* @param {Number} [config.timeout=1200] Time to wait for focus condition
|
|
129
133
|
* @param {Number} [config.interval=200] Time between checks
|
|
130
134
|
*/
|
|
131
|
-
waitForFocused (target, {targetName = 'item', timeoutMsg = `timed out waiting for ${targetName} focused`, timeout = 1200, interval = 200} = {}) {
|
|
132
|
-
browser.waitUntil(() => target.isExisting() && target.isFocused(), {timeout, timeoutMsg, interval});
|
|
135
|
+
async waitForFocused (target, {targetName = 'item', timeoutMsg = `timed out waiting for ${targetName} focused`, timeout = 1200, interval = 200} = {}) {
|
|
136
|
+
await browser.waitUntil(() => target.isExisting() && target.isFocused(), {timeout, timeoutMsg, interval});
|
|
133
137
|
}
|
|
134
138
|
|
|
135
|
-
waitTransitionEnd (delay = 3000, msg = 'timed out waiting for transitionend', callback, ignore = ['opacity', 'filter']) {
|
|
136
|
-
browser.execute(
|
|
139
|
+
async waitTransitionEnd (delay = 3000, msg = 'timed out waiting for transitionend', callback, ignore = ['opacity', 'filter']) {
|
|
140
|
+
await browser.execute(
|
|
137
141
|
// eslint-disable-next-line no-shadow
|
|
138
142
|
function (ignore) {
|
|
139
143
|
window.ontransitionend = function (evt) {
|
|
@@ -148,7 +152,7 @@ class Page {
|
|
|
148
152
|
if (callback) {
|
|
149
153
|
callback();
|
|
150
154
|
}
|
|
151
|
-
browser.waitUntil(
|
|
155
|
+
await browser.waitUntil(
|
|
152
156
|
function () {
|
|
153
157
|
return browser.execute(
|
|
154
158
|
function () {
|
package/utils/runTest.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
const crypto = require('crypto');
|
|
2
3
|
const parseArgs = require('minimist');
|
|
3
4
|
|
|
4
5
|
const args = parseArgs(process.argv);
|
|
@@ -14,13 +15,14 @@ const runTest = ({concurrency, filter, Page, testName, ...rest}) => {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
describe(testName, function () {
|
|
17
|
-
it('should fetch test cases', function () {
|
|
18
|
-
Page.open('?request');
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
it('should fetch test cases', async function () {
|
|
19
|
+
await Page.open('?request');
|
|
20
|
+
|
|
21
|
+
let testCases = await browser.execute(async function () {
|
|
22
|
+
return await window.__TEST_DATA; // eslint-disable-line no-undef
|
|
21
23
|
});
|
|
22
24
|
|
|
23
|
-
expect(testCases).to.be.an('object', 'Test data failed to load');
|
|
25
|
+
await expect(testCases).to.be.an('object', 'Test data failed to load');
|
|
24
26
|
|
|
25
27
|
describe(testName, function () {
|
|
26
28
|
for (const component in testCases) {
|
|
@@ -42,18 +44,30 @@ const runTest = ({concurrency, filter, Page, testName, ...rest}) => {
|
|
|
42
44
|
if (titlePattern && !testCase.title.match(titlePattern)) {
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
|
-
it(`${component}~/${testName}~/${testCase.title}`, function () {
|
|
47
|
+
it(`${component}~/${testName}~/${testCase.title}`, async function () {
|
|
46
48
|
const params = Page.serializeParams(Object.assign({
|
|
47
49
|
component,
|
|
48
50
|
testId
|
|
49
51
|
}, rest));
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
const testNameParts = testCase.title.split('~/');
|
|
54
|
+
let testCaseName = testNameParts.pop();
|
|
55
|
+
// Replace problematic filenames. Windows is much more restrictive.
|
|
56
|
+
testCaseName = testCaseName.replace(/[/\\:?*"|<>]/g, '_');
|
|
57
|
+
// shorten the name with a little bit of leading context to help find the file manually if necessary
|
|
58
|
+
testCaseName = testCaseName.substring(0, 128) + '-' + crypto.createHash('md5').update(testCaseName).digest('hex');
|
|
59
|
+
const screenshotFileName = (component + '/' + testName + '/' + testCaseName);
|
|
52
60
|
|
|
53
|
-
const context = {params, component, testName, url: Page.url};
|
|
61
|
+
const context = {params, component, testName, url: Page.url, fileName: screenshotFileName};
|
|
54
62
|
this.test.context = context;
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
|
|
64
|
+
await Page.open(`?${params}`);
|
|
65
|
+
|
|
66
|
+
expect(await browser.checkScreen(screenshotFileName, {
|
|
67
|
+
disableCSSAnimation: true,
|
|
68
|
+
ignoreNothing: true,
|
|
69
|
+
rawMisMatchPercentage: true
|
|
70
|
+
})).to.equal(0);
|
|
57
71
|
});
|
|
58
72
|
});
|
|
59
73
|
});
|
package/utils/selector.js
CHANGED
|
@@ -42,15 +42,15 @@ const getSubComponent = curry((opts, el) => element(
|
|
|
42
42
|
|
|
43
43
|
// Given two elements, determine if the first element is to the left of the second element
|
|
44
44
|
// element, element => Boolean
|
|
45
|
-
function expectOrdering (firstElement, secondElement) {
|
|
46
|
-
expect(firstElement.getLocation().x < secondElement.getLocation().x).to.be.true();
|
|
45
|
+
async function expectOrdering (firstElement, secondElement) {
|
|
46
|
+
expect(await firstElement.getLocation().x < secondElement.getLocation().x).to.be.true();
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const hasClass = curry((className, el) => {
|
|
49
|
+
const hasClass = curry(async (className, el) => {
|
|
50
50
|
if (className[0] === '.') {
|
|
51
51
|
className = className.slice(1);
|
|
52
52
|
}
|
|
53
|
-
const elementClass = el.getAttribute('className') || el.getAttribute('class');
|
|
53
|
+
const elementClass = await el.getAttribute('className') || await el.getAttribute('class');
|
|
54
54
|
return elementClass.includes(className);
|
|
55
55
|
});
|
|
56
56
|
|