Skip to content

Commit e0f4614

Browse files
noomorphrotemmiz
authored andcommitted
Screenshots and video recordings of tests (#734)
1 parent 4660ab5 commit e0f4614

File tree

100 files changed

+4171
-559
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+4171
-559
lines changed

.travis.yml

+9-4
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,23 @@ matrix:
88
osx_image: xcode9.3
99
env:
1010
- REACT_NATIVE_VERSION=0.53.3
11+
- PATH=$PATH:~/Library/Python/2.7/bin
1112
install:
12-
- ./scripts/install.ios.sh
13+
- ./scripts/install.ios.sh
1314
script:
14-
- ./scripts/ci.ios.sh
15+
- ./scripts/ci.ios.sh
16+
- ./scripts/upload_artifact.sh
1517
- language: objective-c
1618
os: osx
1719
osx_image: xcode9.3
1820
env:
1921
- REACT_NATIVE_VERSION=0.51.1
22+
- PATH=$PATH:~/Library/Python/2.7/bin
2023
install:
21-
- ./scripts/install.ios.sh
24+
- ./scripts/install.ios.sh
2225
script:
23-
- ./scripts/ci.ios.sh
26+
- ./scripts/ci.ios.sh
27+
- ./scripts/upload_artifact.sh
2428
- language: android
2529
os: linux
2630
jdk: oraclejdk8
@@ -69,6 +73,7 @@ branches:
6973
- master
7074
before_install:
7175
- nvm install 8
76+
- pip install awscli --upgrade --user
7277
notifications:
7378
email: true
7479
slack:

detox/.npmignore

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
test
2-
ios/EarlGrey/Demo
3-
ios/EarlGrey/docs
4-
ios/EarlGrey/OCHamcrest.framework
5-
ios/EarlGrey/fishhook
6-
ios/EarlGrey/Tests/UnitTests/ocmock
7-
ios/EarlGrey/Tests/UnitTests/TestRig/Resources
8-
ios/EarlGrey/Tests/FunctionalTests/TestRig/Resources
9-
ios/
2+
/ios/
103

114
build/
125
DerivedData/
@@ -15,6 +8,7 @@ Detox.framework/
158

169

1710
src/**/*.test.js
11+
__snapshots__
1812
ios_src
1913

2014
#################

detox/local-cli/detox-init.js

+105-23
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,119 @@
1-
const fs = require('fs');
2-
const program = require('commander');
3-
const mochaTemplates = require('./templates/mocha.js');
1+
const _ = require("lodash");
2+
const log = require("npmlog");
3+
const fs = require("fs");
4+
const path = require("path");
5+
const program = require("commander");
6+
const mochaTemplates = require("./templates/mocha");
7+
const jestTemplates = require("./templates/jest");
8+
9+
const PREFIX = "detox-init";
410

511
program
6-
.option('-r, --runner [runner]', 'Test runner (currently supports mocha)', 'mocha')
12+
.name('detox init')
13+
.description("Scaffolds initial E2E test folder structure for a specific test runner")
14+
.usage('-r <test-runner-name>')
15+
.option(
16+
"-r, --runner <test-runner-name>",
17+
"test runner name (supported values: mocha, jest)"
18+
)
719
.parse(process.argv);
820

9-
function createFile(dir, content) {
21+
if (program.runner) {
22+
main(program);
23+
} else {
24+
program.help();
25+
}
26+
27+
function createFolder(dir, files) {
28+
if (!fs.existsSync(dir)) {
29+
fs.mkdirSync(dir);
30+
31+
for (const entry of Object.entries(files)) {
32+
const [filename, content] = entry;
33+
createFile(path.join(dir, filename), content);
34+
}
35+
} else {
36+
log.error(PREFIX, "./e2e folder already exists at path: %s", path.resolve(dir));
37+
}
38+
}
39+
40+
function createFile(filename, content) {
1041
try {
11-
fs.writeFileSync(dir, content);
12-
console.log(`A file was created in "${dir}" `);
42+
fs.writeFileSync(filename, content);
43+
log.info(PREFIX, "A file was created in: %s", filename);
44+
} catch (e) {
45+
log.error(PREFIX, "Failed to create file in: %s.", filename);
46+
log.error(PREFIX, e);
47+
}
48+
}
49+
50+
function createMochaFolderE2E() {
51+
createFolder("e2e", {
52+
"mocha.opts": mochaTemplates.runnerConfig,
53+
"init.js": mochaTemplates.initjs,
54+
"firstTest.spec.js": mochaTemplates.firstTest
55+
});
56+
}
57+
58+
function createJestFolderE2E() {
59+
createFolder("e2e", {
60+
"config.json": jestTemplates.runnerConfig,
61+
"init.js": jestTemplates.initjs,
62+
"firstTest.spec.js": jestTemplates.firstTest
63+
});
64+
}
65+
66+
function parsePackageJson(filepath) {
67+
try {
68+
return require(filepath);
1369
} catch (err) {
14-
return err;
70+
log.error(PREFIX, `Failed to parse ./package.json due to the error:\n%s`, err.message);
1571
}
1672
}
1773

18-
const dir = './e2e';
74+
function patchPackageJson(packageJson, runnerName) {
75+
_.set(packageJson, ['detox', 'test-runner'], runnerName);
1976

20-
function createFolder(firstTestContent, runnerConfig, initjsContent) {
21-
if (!fs.existsSync(dir)) {
22-
fs.mkdirSync(dir);
23-
createFile("./e2e/mocha.opts", runnerConfig);
24-
createFile("./e2e/init.js", initjsContent);
25-
createFile("./e2e/firstTest.spec.js", firstTestContent)
26-
} else {
27-
return console.log('e2e folder already exists')
77+
log.info(PREFIX, 'Patched ./package.json with the command:');
78+
log.info(PREFIX, `_.set(packageJson, ['detox', 'test-runner'], "${runnerName}")`);
79+
}
80+
81+
function savePackageJson(filepath, json) {
82+
try {
83+
fs.writeFileSync(filepath, JSON.stringify(json, null, 2));
84+
} catch (err) {
85+
log.error(PREFIX, 'Failed to write changes into ./package.json due to the error:\n%s', err.message);
2886
}
2987
}
3088

31-
switch (program.runner) {
32-
case 'mocha':
33-
createFolder(mochaTemplates.firstTest, mochaTemplates.runnerConfig, mochaTemplates.initjs);
34-
break;
35-
default:
36-
createFolder(mochaTemplates.firstTest, mochaTemplates.runnerConfig, mochaTemplates.initjs);
89+
function patchTestRunnerFieldInPackageJSON(runnerName) {
90+
const packageJsonPath = path.join(process.cwd(), 'package.json');
91+
const packageJson = parsePackageJson(packageJsonPath);
92+
93+
if (packageJson) {
94+
patchPackageJson(packageJson, runnerName);
95+
savePackageJson(packageJsonPath, packageJson);
96+
}
97+
}
98+
99+
function main({ runner }) {
100+
switch (runner) {
101+
case "mocha":
102+
createMochaFolderE2E();
103+
patchTestRunnerFieldInPackageJSON("mocha");
104+
break;
105+
case "jest":
106+
createJestFolderE2E();
107+
patchTestRunnerFieldInPackageJSON("jest");
108+
break;
109+
default:
110+
log.error(PREFIX, "Convenience scaffolding for `%s` test runner is not supported currently.\n", runner);
111+
log.info(PREFIX, "Supported runners at the moment are `mocha` and `jest`:");
112+
log.info(PREFIX, "* detox init -r mocha");
113+
log.info(PREFIX, "* detox init -r jest\n");
114+
log.info(PREFIX, "If it is not a typo, and you plan to work with `%s` runner, then you have to create test setup files manually.", runner);
115+
log.info(PREFIX, "HINT: Try running one of the commands above, watch what it does, and do the similar steps for your use case.");
116+
117+
break;
118+
}
37119
}

detox/local-cli/detox-test.js

+29-19
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@ const path = require('path');
55
const cp = require('child_process');
66
const fs = require('fs-extra');
77
const _ = require('lodash');
8-
const CustomError = require('../src/errors/CustomError');
98
const environment = require('../src/utils/environment');
9+
const buildDefaultArtifactsRootDirpath = require('../src/artifacts/utils/buildDefaultArtifactsRootDirpath');
10+
const DetoxConfigError = require('../src/errors/DetoxConfigError');
1011
const config = require(path.join(process.cwd(), 'package.json')).detox;
1112

12-
class DetoxConfigError extends CustomError {}
13-
14-
15-
1613
program
1714
.option('-o, --runner-config [config]',
1815
`Test runner config file, defaults to e2e/mocha.opts for mocha and e2e/config.json' for jest`)
@@ -30,7 +27,13 @@ program
3027
'When an action/expectation takes a significant amount of time use this option to print device synchronization status.'
3128
+ 'The status will be printed if the action takes more than [value]ms to complete')
3229
.option('-a, --artifacts-location [path]',
33-
'Artifacts destination path (currently will contain only logs). If the destination already exists, it will be removed first')
30+
'[EXPERIMENTAL] Artifacts (logs, screenshots, etc) root directory.', 'artifacts')
31+
.option('--record-logs [failing|all|none]',
32+
'[EXPERIMENTAL] Save logs during each test to artifacts directory. Pass "failing" to save logs of failing tests only.')
33+
.option('--take-screenshots [failing|all|none]',
34+
'[EXPERIMENTAL] Save screenshots before and after each test to artifacts directory. Pass "failing" to save screenshots of failing tests only.')
35+
.option('--record-videos [failing|all|none]',
36+
'[EXPERIMENTAL] Save screen recordings of each test to artifacts directory. Pass "failing" to save recordings of failing tests only.')
3437
.option('-p, --platform [ios/android]',
3538
'[DEPRECATED], platform is deduced automatically. Run platform specific tests. Runs tests with invert grep on \':platform:\', '
3639
+ 'e.g test with substring \':ios:\' in its name will not run when passing \'--platform android\'')
@@ -42,18 +45,18 @@ program
4245
'[iOS Only] Specifies number of workers the test runner should spawn, requires a test runner with parallel execution support (Detox CLI currently supports Jest)', 1)
4346
.parse(process.argv);
4447

48+
program.artifactsLocation = buildDefaultArtifactsRootDirpath(program.configuration, program.artifactsLocation);
4549

4650
clearDeviceRegistryLockFile();
4751

52+
if (!program.configuration) {
53+
throw new DetoxConfigError(`Cannot determine which configuration to use.
54+
Use --configuration to choose one of the following: ${_.keys(config.configurations).join(', ')}`);
55+
}
4856

49-
if (program.configuration) {
50-
if (!config.configurations[program.configuration]) {
51-
throw new DetoxConfigError(`Cannot determine configuration '${program.configuration}'.
57+
if (!config.configurations[program.configuration]) {
58+
throw new DetoxConfigError(`Cannot determine configuration '${program.configuration}'.
5259
Available configurations: ${_.keys(config.configurations).join(', ')}`);
53-
}
54-
} else if(!program.configuration) {
55-
throw new DetoxConfigError(`Cannot determine which configuration to use.
56-
Use --configuration to choose one of the following: ${_.keys(config.configurations).join(', ')}`);
5760
}
5861

5962
const testFolder = getConfigFor(['file', 'specs'], 'e2e');
@@ -102,15 +105,21 @@ function runMocha() {
102105
const configuration = program.configuration ? `--configuration ${program.configuration}` : '';
103106
const cleanup = program.cleanup ? `--cleanup` : '';
104107
const reuse = program.reuse ? `--reuse` : '';
105-
const artifactsLocation = program.artifactsLocation ? `--artifacts-location ${program.artifactsLocation}` : '';
108+
const artifactsLocation = program.artifactsLocation ? `--artifacts-location "${program.artifactsLocation}"` : '';
106109
const configFile = runnerConfig ? `--opts ${runnerConfig}` : '';
107110
const platformString = platform ? `--grep ${getPlatformSpecificString(platform)} --invert` : '';
111+
const logs = program.recordLogs ? `--record-logs ${program.recordLogs}` : '';
112+
const screenshots = program.takeScreenshots ? `--take-screenshots ${program.takeScreenshots}` : '';
113+
const videos = program.recordVideos ? `--record-videos ${program.recordVideos}` : '';
108114
const headless = program.headless ? `--headless` : '';
109115

110116
const debugSynchronization = program.debugSynchronization ? `--debug-synchronization ${program.debugSynchronization}` : '';
111117
const binPath = path.join('node_modules', '.bin', 'mocha');
112-
const command = `${binPath} ${testFolder} ${configFile} ${configuration} ${loglevel} ${cleanup} ${reuse} ${debugSynchronization} ${platformString} ${artifactsLocation} ${headless}`;
118+
const command = `${binPath} ${testFolder} ${configFile} ${configuration} ${loglevel} ${cleanup} ` +
119+
`${reuse} ${debugSynchronization} ${platformString} ${headless} ` +
120+
`${logs} ${screenshots} ${videos} ${artifactsLocation}`;
113121

122+
console.log(command);
114123
cp.execSync(command, {stdio: 'inherit'});
115124
}
116125

@@ -126,15 +135,17 @@ function runJest() {
126135
cleanup: program.cleanup,
127136
reuse: program.reuse,
128137
debugSynchronization: program.debugSynchronization,
138+
headless: program.headless,
129139
artifactsLocation: program.artifactsLocation,
130-
headless: program.headless
140+
recordLogs: program.recordLogs,
141+
takeScreenshots: program.takeScreenshots,
142+
recordVideos: program.recordVideos,
131143
});
132144

133145
console.log(command);
134-
135146
cp.execSync(command, {
136147
stdio: 'inherit',
137-
env
148+
env,
138149
});
139150
}
140151

@@ -165,7 +176,6 @@ function getPlatformSpecificString(platform) {
165176
return platformRevertString;
166177
}
167178

168-
169179
function clearDeviceRegistryLockFile() {
170180
const lockFilePath = environment.getDeviceLockFilePath();
171181
fs.ensureFileSync(lockFilePath);

detox/local-cli/detox.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ const program = require('commander');
55
program
66
.arguments('<process>')
77
.command('test', 'Initiating your test suite')
8+
.command('init', '[convenience method] Scaffold initial e2e tests folder')
89
.command('build', `[convenience method] Run the command defined in 'configuration.build'`)
910
.command('run-server', 'Starts a standalone detox server')
10-
.command('init', 'Create initial e2e tests folder')
1111
.command('clean-framework-cache', `Delete all compiled framework binaries from ~/Library/Detox, they will be rebuilt on 'npm install' or when running 'build-framework-cache'`)
1212
.command('build-framework-cache', `Build Detox.framework to ~/Library/Detox. The framework cache is specific for each combination of Xcode and Detox versions`)
1313
.parse(process.argv);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const firstTestContent = `describe('Example', () => {
2+
beforeEach(async () => {
3+
await device.reloadReactNative();
4+
});
5+
6+
it('should have welcome screen', async () => {
7+
await expect(element(by.id('welcome'))).toBeVisible();
8+
});
9+
10+
it('should show hello screen after tap', async () => {
11+
await element(by.id('hello_button')).tap();
12+
await expect(element(by.text('Hello!!!'))).toBeVisible();
13+
});
14+
15+
it('should show world screen after tap', async () => {
16+
await element(by.id('world_button')).tap();
17+
await expect(element(by.text('World!!!'))).toBeVisible();
18+
});
19+
})`;
20+
21+
module.exports = firstTestContent;

detox/local-cli/templates/jest.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const firstTestContent = require('./firstTestContent');
2+
const runnerConfig = `{
3+
"setupTestFrameworkScriptFile": "./init.js"
4+
}`;
5+
6+
const initjsContent = `const detox = require('detox');
7+
const config = require('../package.json').detox;
8+
const adapter = require('detox/runners/jest/adapter');
9+
10+
jest.setTimeout(120000);
11+
jasmine.getEnv().addReporter(adapter);
12+
13+
beforeAll(async () => {
14+
await detox.init(config);
15+
});
16+
17+
beforeEach(async () => {
18+
await adapter.beforeEach();
19+
});
20+
21+
afterAll(async () => {
22+
await adapter.afterAll();
23+
await detox.cleanup();
24+
});`;
25+
26+
exports.initjs = initjsContent;
27+
exports.firstTest = firstTestContent;
28+
exports.runnerConfig = runnerConfig;

0 commit comments

Comments
 (0)