Skip to content

Commit

Permalink
Screenshots and video recordings of tests (wix#734)
Browse files Browse the repository at this point in the history
  • Loading branch information
noomorph authored and rotemmiz committed Jun 20, 2018
1 parent 4660ab5 commit e0f4614
Show file tree
Hide file tree
Showing 100 changed files with 4,171 additions and 559 deletions.
13 changes: 9 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@ matrix:
osx_image: xcode9.3
env:
- REACT_NATIVE_VERSION=0.53.3
- PATH=$PATH:~/Library/Python/2.7/bin
install:
- ./scripts/install.ios.sh
- ./scripts/install.ios.sh
script:
- ./scripts/ci.ios.sh
- ./scripts/ci.ios.sh
- ./scripts/upload_artifact.sh
- language: objective-c
os: osx
osx_image: xcode9.3
env:
- REACT_NATIVE_VERSION=0.51.1
- PATH=$PATH:~/Library/Python/2.7/bin
install:
- ./scripts/install.ios.sh
- ./scripts/install.ios.sh
script:
- ./scripts/ci.ios.sh
- ./scripts/ci.ios.sh
- ./scripts/upload_artifact.sh
- language: android
os: linux
jdk: oraclejdk8
Expand Down Expand Up @@ -69,6 +73,7 @@ branches:
- master
before_install:
- nvm install 8
- pip install awscli --upgrade --user
notifications:
email: true
slack:
Expand Down
10 changes: 2 additions & 8 deletions detox/.npmignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
test
ios/EarlGrey/Demo
ios/EarlGrey/docs
ios/EarlGrey/OCHamcrest.framework
ios/EarlGrey/fishhook
ios/EarlGrey/Tests/UnitTests/ocmock
ios/EarlGrey/Tests/UnitTests/TestRig/Resources
ios/EarlGrey/Tests/FunctionalTests/TestRig/Resources
ios/
/ios/

build/
DerivedData/
Expand All @@ -15,6 +8,7 @@ Detox.framework/


src/**/*.test.js
__snapshots__
ios_src

#################
Expand Down
128 changes: 105 additions & 23 deletions detox/local-cli/detox-init.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,119 @@
const fs = require('fs');
const program = require('commander');
const mochaTemplates = require('./templates/mocha.js');
const _ = require("lodash");
const log = require("npmlog");
const fs = require("fs");
const path = require("path");
const program = require("commander");
const mochaTemplates = require("./templates/mocha");
const jestTemplates = require("./templates/jest");

const PREFIX = "detox-init";

program
.option('-r, --runner [runner]', 'Test runner (currently supports mocha)', 'mocha')
.name('detox init')
.description("Scaffolds initial E2E test folder structure for a specific test runner")
.usage('-r <test-runner-name>')
.option(
"-r, --runner <test-runner-name>",
"test runner name (supported values: mocha, jest)"
)
.parse(process.argv);

function createFile(dir, content) {
if (program.runner) {
main(program);
} else {
program.help();
}

function createFolder(dir, files) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);

for (const entry of Object.entries(files)) {
const [filename, content] = entry;
createFile(path.join(dir, filename), content);
}
} else {
log.error(PREFIX, "./e2e folder already exists at path: %s", path.resolve(dir));
}
}

function createFile(filename, content) {
try {
fs.writeFileSync(dir, content);
console.log(`A file was created in "${dir}" `);
fs.writeFileSync(filename, content);
log.info(PREFIX, "A file was created in: %s", filename);
} catch (e) {
log.error(PREFIX, "Failed to create file in: %s.", filename);
log.error(PREFIX, e);
}
}

function createMochaFolderE2E() {
createFolder("e2e", {
"mocha.opts": mochaTemplates.runnerConfig,
"init.js": mochaTemplates.initjs,
"firstTest.spec.js": mochaTemplates.firstTest
});
}

function createJestFolderE2E() {
createFolder("e2e", {
"config.json": jestTemplates.runnerConfig,
"init.js": jestTemplates.initjs,
"firstTest.spec.js": jestTemplates.firstTest
});
}

function parsePackageJson(filepath) {
try {
return require(filepath);
} catch (err) {
return err;
log.error(PREFIX, `Failed to parse ./package.json due to the error:\n%s`, err.message);
}
}

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

function createFolder(firstTestContent, runnerConfig, initjsContent) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
createFile("./e2e/mocha.opts", runnerConfig);
createFile("./e2e/init.js", initjsContent);
createFile("./e2e/firstTest.spec.js", firstTestContent)
} else {
return console.log('e2e folder already exists')
log.info(PREFIX, 'Patched ./package.json with the command:');
log.info(PREFIX, `_.set(packageJson, ['detox', 'test-runner'], "${runnerName}")`);
}

function savePackageJson(filepath, json) {
try {
fs.writeFileSync(filepath, JSON.stringify(json, null, 2));
} catch (err) {
log.error(PREFIX, 'Failed to write changes into ./package.json due to the error:\n%s', err.message);
}
}

switch (program.runner) {
case 'mocha':
createFolder(mochaTemplates.firstTest, mochaTemplates.runnerConfig, mochaTemplates.initjs);
break;
default:
createFolder(mochaTemplates.firstTest, mochaTemplates.runnerConfig, mochaTemplates.initjs);
function patchTestRunnerFieldInPackageJSON(runnerName) {
const packageJsonPath = path.join(process.cwd(), 'package.json');
const packageJson = parsePackageJson(packageJsonPath);

if (packageJson) {
patchPackageJson(packageJson, runnerName);
savePackageJson(packageJsonPath, packageJson);
}
}

function main({ runner }) {
switch (runner) {
case "mocha":
createMochaFolderE2E();
patchTestRunnerFieldInPackageJSON("mocha");
break;
case "jest":
createJestFolderE2E();
patchTestRunnerFieldInPackageJSON("jest");
break;
default:
log.error(PREFIX, "Convenience scaffolding for `%s` test runner is not supported currently.\n", runner);
log.info(PREFIX, "Supported runners at the moment are `mocha` and `jest`:");
log.info(PREFIX, "* detox init -r mocha");
log.info(PREFIX, "* detox init -r jest\n");
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);
log.info(PREFIX, "HINT: Try running one of the commands above, watch what it does, and do the similar steps for your use case.");

break;
}
}
48 changes: 29 additions & 19 deletions detox/local-cli/detox-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@ const path = require('path');
const cp = require('child_process');
const fs = require('fs-extra');
const _ = require('lodash');
const CustomError = require('../src/errors/CustomError');
const environment = require('../src/utils/environment');
const buildDefaultArtifactsRootDirpath = require('../src/artifacts/utils/buildDefaultArtifactsRootDirpath');
const DetoxConfigError = require('../src/errors/DetoxConfigError');
const config = require(path.join(process.cwd(), 'package.json')).detox;

class DetoxConfigError extends CustomError {}



program
.option('-o, --runner-config [config]',
`Test runner config file, defaults to e2e/mocha.opts for mocha and e2e/config.json' for jest`)
Expand All @@ -30,7 +27,13 @@ program
'When an action/expectation takes a significant amount of time use this option to print device synchronization status.'
+ 'The status will be printed if the action takes more than [value]ms to complete')
.option('-a, --artifacts-location [path]',
'Artifacts destination path (currently will contain only logs). If the destination already exists, it will be removed first')
'[EXPERIMENTAL] Artifacts (logs, screenshots, etc) root directory.', 'artifacts')
.option('--record-logs [failing|all|none]',
'[EXPERIMENTAL] Save logs during each test to artifacts directory. Pass "failing" to save logs of failing tests only.')
.option('--take-screenshots [failing|all|none]',
'[EXPERIMENTAL] Save screenshots before and after each test to artifacts directory. Pass "failing" to save screenshots of failing tests only.')
.option('--record-videos [failing|all|none]',
'[EXPERIMENTAL] Save screen recordings of each test to artifacts directory. Pass "failing" to save recordings of failing tests only.')
.option('-p, --platform [ios/android]',
'[DEPRECATED], platform is deduced automatically. Run platform specific tests. Runs tests with invert grep on \':platform:\', '
+ 'e.g test with substring \':ios:\' in its name will not run when passing \'--platform android\'')
Expand All @@ -42,18 +45,18 @@ program
'[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)
.parse(process.argv);

program.artifactsLocation = buildDefaultArtifactsRootDirpath(program.configuration, program.artifactsLocation);

clearDeviceRegistryLockFile();

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

if (program.configuration) {
if (!config.configurations[program.configuration]) {
throw new DetoxConfigError(`Cannot determine configuration '${program.configuration}'.
if (!config.configurations[program.configuration]) {
throw new DetoxConfigError(`Cannot determine configuration '${program.configuration}'.
Available configurations: ${_.keys(config.configurations).join(', ')}`);
}
} else if(!program.configuration) {
throw new DetoxConfigError(`Cannot determine which configuration to use.
Use --configuration to choose one of the following: ${_.keys(config.configurations).join(', ')}`);
}

const testFolder = getConfigFor(['file', 'specs'], 'e2e');
Expand Down Expand Up @@ -102,15 +105,21 @@ function runMocha() {
const configuration = program.configuration ? `--configuration ${program.configuration}` : '';
const cleanup = program.cleanup ? `--cleanup` : '';
const reuse = program.reuse ? `--reuse` : '';
const artifactsLocation = program.artifactsLocation ? `--artifacts-location ${program.artifactsLocation}` : '';
const artifactsLocation = program.artifactsLocation ? `--artifacts-location "${program.artifactsLocation}"` : '';
const configFile = runnerConfig ? `--opts ${runnerConfig}` : '';
const platformString = platform ? `--grep ${getPlatformSpecificString(platform)} --invert` : '';
const logs = program.recordLogs ? `--record-logs ${program.recordLogs}` : '';
const screenshots = program.takeScreenshots ? `--take-screenshots ${program.takeScreenshots}` : '';
const videos = program.recordVideos ? `--record-videos ${program.recordVideos}` : '';
const headless = program.headless ? `--headless` : '';

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

console.log(command);
cp.execSync(command, {stdio: 'inherit'});
}

Expand All @@ -126,15 +135,17 @@ function runJest() {
cleanup: program.cleanup,
reuse: program.reuse,
debugSynchronization: program.debugSynchronization,
headless: program.headless,
artifactsLocation: program.artifactsLocation,
headless: program.headless
recordLogs: program.recordLogs,
takeScreenshots: program.takeScreenshots,
recordVideos: program.recordVideos,
});

console.log(command);

cp.execSync(command, {
stdio: 'inherit',
env
env,
});
}

Expand Down Expand Up @@ -165,7 +176,6 @@ function getPlatformSpecificString(platform) {
return platformRevertString;
}


function clearDeviceRegistryLockFile() {
const lockFilePath = environment.getDeviceLockFilePath();
fs.ensureFileSync(lockFilePath);
Expand Down
2 changes: 1 addition & 1 deletion detox/local-cli/detox.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ const program = require('commander');
program
.arguments('<process>')
.command('test', 'Initiating your test suite')
.command('init', '[convenience method] Scaffold initial e2e tests folder')
.command('build', `[convenience method] Run the command defined in 'configuration.build'`)
.command('run-server', 'Starts a standalone detox server')
.command('init', 'Create initial e2e tests folder')
.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'`)
.command('build-framework-cache', `Build Detox.framework to ~/Library/Detox. The framework cache is specific for each combination of Xcode and Detox versions`)
.parse(process.argv);
21 changes: 21 additions & 0 deletions detox/local-cli/templates/firstTestContent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const firstTestContent = `describe('Example', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should have welcome screen', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
it('should show hello screen after tap', async () => {
await element(by.id('hello_button')).tap();
await expect(element(by.text('Hello!!!'))).toBeVisible();
});
it('should show world screen after tap', async () => {
await element(by.id('world_button')).tap();
await expect(element(by.text('World!!!'))).toBeVisible();
});
})`;

module.exports = firstTestContent;
28 changes: 28 additions & 0 deletions detox/local-cli/templates/jest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const firstTestContent = require('./firstTestContent');
const runnerConfig = `{
"setupTestFrameworkScriptFile": "./init.js"
}`;

const initjsContent = `const detox = require('detox');
const config = require('../package.json').detox;
const adapter = require('detox/runners/jest/adapter');
jest.setTimeout(120000);
jasmine.getEnv().addReporter(adapter);
beforeAll(async () => {
await detox.init(config);
});
beforeEach(async () => {
await adapter.beforeEach();
});
afterAll(async () => {
await adapter.afterAll();
await detox.cleanup();
});`;

exports.initjs = initjsContent;
exports.firstTest = firstTestContent;
exports.runnerConfig = runnerConfig;
Loading

0 comments on commit e0f4614

Please sign in to comment.