Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] WebDriver implementation #4142

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .buildkite/jobs/pipeline.ios_rn_70.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
- label: ":ios::detox: RN .70 + iOS: Tests app"
command:
- "nvm install"
- "./scripts/ci.ios.sh"
env:
REACT_NATIVE_VERSION: 0.70.7
artifact_paths:
Expand Down
5 changes: 3 additions & 2 deletions .buildkite/jobs/pipeline.ios_rn_71.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
- label: ":ios::detox: RN .71 + iOS: Tests app"
command:
- "nvm install"
- "./scripts/ci.ios.sh"
- "ping 46.101.117.193"
env:
DETOX_DISABLE_POD_INSTALL: true
DETOX_DISABLE_POSTINSTALL: true
REACT_NATIVE_VERSION: 0.71.10
artifact_paths:
- "/Users/builder/work/coverage/**/*.lcov"
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,5 @@ detox/test/artifacts
Detox-android/
Detox-ios-src.tbz
Detox-ios.tbz

dist/
3 changes: 2 additions & 1 deletion detox/jsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"checkJs": true,
"target": "es2017"
Expand All @@ -10,4 +11,4 @@
"include": [
"src/**/*"
]
}
}
11 changes: 6 additions & 5 deletions detox/local-cli/run-server.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const collectCliConfig = require('../src/configuration/collectCliConfig');
const composeLoggerConfig = require('../src/configuration/composeLoggerConfig');
const { DetoxRuntimeError } = require('../src/errors');
const DetoxServer = require('../src/server/DetoxServer');
const logger = require('../src/utils/logger');
const WebDriverServer = require('../src/webdriver');

module.exports.command = 'run-server';
module.exports.desc = 'Start a standalone Detox server';
Expand All @@ -18,7 +18,7 @@ module.exports.builder = {
describe: 'Port number',
group: 'Configuration:',
number: true,
default: 8099
default: 5789
},
'no-color': {
describe: 'Disable colorful logs',
Expand All @@ -39,8 +39,9 @@ module.exports.handler = async function runServer(argv) {
cliConfig: collectCliConfig({ argv }),
}));

await new DetoxServer({
await new WebDriverServer({
port: +argv.port,
standalone: true,
}).open();
}).startServer();

console.log(`Detox server listening on localhost:${argv.port}...`);
};
9 changes: 8 additions & 1 deletion detox/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "detox",
"description": "E2E tests and automation for mobile",
"version": "20.11.0",
"version": "22.0.0",
"bin": {
"detox": "local-cli/cli.js"
},
Expand Down Expand Up @@ -56,17 +56,20 @@
"prettier": "^2.4.1",
"react-native": "0.71.10",
"react-native-codegen": "^0.0.8",
"ts-jest": "^28.0.0",
"typescript": "^4.5.2",
"wtfnode": "^0.9.1"
},
"dependencies": {
"ajv": "^8.6.3",
"axios": "^1.4.0",
"bunyan": "^1.8.12",
"bunyan-debug-stream": "^3.1.0",
"caf": "^15.0.1",
"chalk": "^4.0.0",
"child-process-promise": "^2.2.0",
"execa": "^5.1.1",
"express": "^4.18.2",
"find-up": "^5.0.0",
"fs-extra": "^11.0.0",
"funpermaproxy": "^1.1.0",
Expand All @@ -89,6 +92,7 @@
"telnet-client": "1.2.8",
"tempfile": "^2.0.0",
"trace-event-lib": "^1.3.1",
"unzipper": "^0.10.14",
"which": "^1.3.1",
"ws": "^7.0.0",
"yargs": "^17.0.0",
Expand All @@ -110,6 +114,9 @@
"setupFiles": [
"<rootDir>/__tests__/setupJest.js"
],
"transform": {
"^.+\\.ts$": "ts-jest"
},
"testEnvironment": "node",
"testRunner": "jest-circus/runner",
"roots": [
Expand Down
4 changes: 1 addition & 3 deletions detox/src/client/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ const actions = require('./actions/actions');

class Client {
/**
* @param {number} debugSynchronization
* @param {string} server
* @param {string} sessionId
* @param {DetoxInternals.RuntimeConfig['session']} config
*/
constructor({ debugSynchronization, server, sessionId }) {
this._onAppConnected = this._onAppConnected.bind(this);
Expand Down
2 changes: 1 addition & 1 deletion detox/src/configuration/composeAppsConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const CLI_PARSER_OPTIONS = {
* @param {*} opts.cliConfig
* @returns {Record<string, Detox.DetoxAppConfig>}
*/
function composeAppsConfig(opts) {
async function composeAppsConfig(opts) {
const appsConfig = composeAppsConfigFromAliased(opts);
overrideAppLaunchArgs(appsConfig, opts.cliConfig);

Expand Down
76 changes: 38 additions & 38 deletions detox/src/configuration/composeAppsConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ describe('composeAppsConfig', () => {
localConfig.app = 'example1';
});

it('should resolve the alias and extract the app config', () => {
expect(compose()).toEqual({
it('should resolve the alias and extract the app config', async () => {
await expect(compose()).resolves.toEqual({
default: globalConfig.apps.example1
});
});
Expand All @@ -74,8 +74,8 @@ describe('composeAppsConfig', () => {
localConfig.app = { ...globalConfig.apps.example2 };
});

it('should resolve the alias and extract the app config', () => {
expect(compose()).toEqual({
it('should resolve the alias and extract the app config', async () => {
await expect(compose()).resolves.toEqual({
default: globalConfig.apps.example2,
});
});
Expand All @@ -96,8 +96,8 @@ describe('composeAppsConfig', () => {
localConfig.apps = ['example1', 'example2'];
});

it('should resolve the alias and extract the app config', () => {
expect(compose()).toEqual({
it('should resolve the alias and extract the app config', async () => {
await expect(compose()).resolves.toEqual({
app1: globalConfig.apps.example1,
app2: globalConfig.apps.example2,
});
Expand All @@ -114,8 +114,8 @@ describe('composeAppsConfig', () => {
cliConfig.appLaunchArgs = '--no-arg2 -arg3=override';
});

it('should parse it and merge the values inside', () => {
const { app1, app2 } = compose();
it('should parse it and merge the values inside', async () => {
const { app1, app2 } = await compose();

expect(app1.launchArgs).toEqual({ arg3: 'override' });
expect(app2.launchArgs).toEqual({ arg1: 'value1', arg3: 'override' });
Expand All @@ -131,8 +131,8 @@ describe('composeAppsConfig', () => {
];
});

it('should resolve the alias and extract the app config', () => {
expect(compose()).toEqual({
it('should resolve the alias and extract the app config', async () => {
await expect(compose()).resolves.toEqual({
app1: globalConfig.apps.example1,
app2: globalConfig.apps.example2,
});
Expand All @@ -151,8 +151,8 @@ describe('composeAppsConfig', () => {
deviceConfig.type = './stub/driver';
});

it('should return an empty app config', () => {
expect(compose()).toEqual({});
it('should return an empty app config', async () => {
await expect(compose()).resolves.toEqual({});
});
});
});
Expand All @@ -173,70 +173,70 @@ describe('composeAppsConfig', () => {
['android.attached'],
['android.emulator'],
['android.genycloud'],
])('no app/apps is defined when device is %s', (deviceType) => {
])('no app/apps is defined when device is %s', async (deviceType) => {
delete localConfig.app;
delete localConfig.apps;
deviceConfig.type = deviceType;

expect(compose).toThrowError(errorComposer.noAppIsDefined(deviceType));
await expect(compose).rejects.toThrowError(errorComposer.noAppIsDefined(deviceType));
});

test('both app/apps are defined', () => {
test('both app/apps are defined', async () => {
localConfig.app = 'example1';
localConfig.apps = ['example1', 'example2'];

expect(compose).toThrowError(errorComposer.ambiguousAppAndApps());
await expect(compose).rejects.toThrowError(errorComposer.ambiguousAppAndApps());
});

test('app is defined as an array', () => {
test('app is defined as an array', async () => {
localConfig.app = ['example1', 'example2'];

expect(compose).toThrowError(errorComposer.multipleAppsConfigArrayTypo());
await expect(compose).rejects.toThrowError(errorComposer.multipleAppsConfigArrayTypo());
});

test('apps are defined as a string', () => {
test('apps are defined as a string', async () => {
localConfig.apps = 'example1';

expect(compose).toThrowError(errorComposer.multipleAppsConfigShouldBeArray());
await expect(compose).rejects.toThrowError(errorComposer.multipleAppsConfigShouldBeArray());
});

test('"apps" dictionary is undefined', () => {
test('"apps" dictionary is undefined', async () => {
delete globalConfig.apps;
localConfig.app = 'example1';

expect(compose).toThrowError(errorComposer.thereAreNoAppConfigs('example1'));
await expect(compose).rejects.toThrowError(errorComposer.thereAreNoAppConfigs('example1'));
});

test('non-existent app, cannot resolve alias', () => {
test('non-existent app, cannot resolve alias', async () => {
localConfig.app = 'elbereth';

expect(compose).toThrowError(errorComposer.cantResolveAppAlias('elbereth'));
await expect(compose).rejects.toThrowError(errorComposer.cantResolveAppAlias('elbereth'));
});

test('undefined inline app', () => {
test('undefined inline app', async () => {
localConfig.apps = ['example1', null];

expect(compose).toThrowError(
await expect(compose).rejects.toThrowError(
errorComposer.appConfigIsUndefined(['configurations', configurationName, 'apps', 1])
);
});

test('apps have no name (collision)', () => {
test('apps have no name (collision)', async () => {
localConfig.apps = ['example1', 'example2'];

expect(compose).toThrowError(errorComposer.duplicateAppConfig({
await expect(compose).rejects.toThrowError(errorComposer.duplicateAppConfig({
appName: undefined,
appPath: ['apps', 'example2'],
preExistingAppPath: ['apps', 'example1'],
}));
});

test('apps have the same name (collision)', () => {
test('apps have the same name (collision)', async () => {
globalConfig.apps.example1.name = 'sameApp';
globalConfig.apps.example2.name = 'sameApp';
localConfig.apps = ['example1', 'example2'];

expect(compose).toThrowError(errorComposer.duplicateAppConfig({
await expect(compose).rejects.toThrowError(errorComposer.duplicateAppConfig({
appName: 'sameApp',
appPath: ['apps', 'example2'],
preExistingAppPath: ['apps', 'example1'],
Expand All @@ -248,13 +248,13 @@ describe('composeAppsConfig', () => {
['android.apk', 'android.attached'],
['android.apk', 'android.emulator'],
['android.apk', 'android.genycloud'],
])('known app (device type = %s) has no binaryPath', (appType, deviceType) => {
])('known app (device type = %s) has no binaryPath', async (appType, deviceType) => {
delete globalConfig.apps.example1.binaryPath;
globalConfig.apps.example1.type = appType;
deviceConfig.type = deviceType;
localConfig.app = 'example1';

expect(compose).toThrowError(errorComposer.missingAppBinaryPath(
await expect(compose).rejects.toThrowError(errorComposer.missingAppBinaryPath(
['apps', 'example1']
));
});
Expand All @@ -264,26 +264,26 @@ describe('composeAppsConfig', () => {
['android.apk', 'android.attached'],
['android.apk', 'android.emulator'],
['android.apk', 'android.genycloud'],
])('known app (device type = %s) has malformed launchArgs', (appType, deviceType) => {
])('known app (device type = %s) has malformed launchArgs', async (appType, deviceType) => {
globalConfig.apps.example1.launchArgs = '-hello -world';
globalConfig.apps.example1.type = appType;
deviceConfig.type = deviceType;
localConfig.app = 'example1';

expect(compose).toThrowError(errorComposer.malformedAppLaunchArgs(
await expect(compose).rejects.toThrowError(errorComposer.malformedAppLaunchArgs(
['apps', 'example1']
));
});

test.each([
['ios.app', 'ios.simulator'],
])('known app (device type = %s) has unsupported reversePorts', (appType, deviceType) => {
])('known app (device type = %s) has unsupported reversePorts', async (appType, deviceType) => {
globalConfig.apps.example1.reversePorts = [3000];
globalConfig.apps.example1.type = appType;
deviceConfig.type = deviceType;
localConfig.app = 'example1';

expect(compose).toThrowError(errorComposer.unsupportedReversePorts(
await expect(compose).rejects.toThrowError(errorComposer.unsupportedReversePorts(
['apps', 'example1']
));
});
Expand All @@ -293,12 +293,12 @@ describe('composeAppsConfig', () => {
['ios.app', 'android.attached'],
['ios.app', 'android.emulator'],
['ios.app', 'android.genycloud'],
])('app type (%s) is incompatible with device (%s)', (appType, deviceType) => {
])('app type (%s) is incompatible with device (%s)', async (appType, deviceType) => {
localConfig.app = 'example1';
globalConfig.apps.example1.type = appType;
deviceConfig.type = deviceType;

expect(compose).toThrowError(
await expect(compose).rejects.toThrowError(
errorComposer.invalidAppType({
appPath: ['apps', 'example1'],
allowedAppTypes: [appType === 'android.apk' ? 'ios.app' : 'android.apk'],
Expand Down
4 changes: 2 additions & 2 deletions detox/src/configuration/composeSessionConfig.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const isValidWebsocketURL = require('../utils/isValidWebsocketURL');
const isValidHTTPUrl = require('../utils/isValidHTTPUrl');

/**
* @param {{
Expand All @@ -18,7 +18,7 @@ async function composeSessionConfig(options) {

if (session.server != null) {
const value = session.server;
if (typeof value !== 'string' || !isValidWebsocketURL(value)) {
if (typeof value !== 'string' || !isValidHTTPUrl(value)) {
throw errorComposer.invalidServerProperty();
}
}
Expand Down
2 changes: 1 addition & 1 deletion detox/src/configuration/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async function composeDetoxConfig({
cliConfig,
});

const appsConfig = composeAppsConfig({
const appsConfig = await composeAppsConfig({
errorComposer,
configurationName,
deviceConfig,
Expand Down
Loading