Skip to content

Commit eb0e670

Browse files
authored
feat(config): support debugSynchronization in .detoxrc (wix#2448)
1 parent 60fd09a commit eb0e670

12 files changed

+449
-147
lines changed

detox/src/client/Client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class Client {
1111
this.configuration = config;
1212
this.ws = new AsyncWebSocket(config.server);
1313
this.slowInvocationStatusHandler = null;
14-
this.slowInvocationTimeout = argparse.getArgValue('debug-synchronization');
14+
this.slowInvocationTimeout = config.debugSynchronization;
1515
this.successfulTestRun = true; // flag for cleanup
1616
this.pandingAppCrash;
1717

detox/src/client/Client.test.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
const tempfile = require('tempfile');
22
const actions = require('./actions/actions');
3-
const config = require('../configuration/configurations.mock').validOneDeviceAndSession.session;
43
const invoke = require('../invoke');
54
const sleep = require('../utils/sleep');
5+
const { validOneDeviceAndSession } = require('../configuration/configurations.mock');
66

77
describe('Client', () => {
8-
let argparse;
98
let WebSocket;
109
let Client;
1110
let client;
1211
let bunyan;
1312
let log;
13+
let sessionConfig;
1414

1515
beforeEach(() => {
16+
sessionConfig = {
17+
...validOneDeviceAndSession.session,
18+
};
19+
1620
jest.mock('../utils/logger');
1721
WebSocket = jest.mock('./AsyncWebSocket');
1822

19-
jest.mock('../utils/argparse');
20-
argparse = require('../utils/argparse');
21-
2223
jest.mock('bunyan')
2324
bunyan = require('bunyan');
2425

@@ -169,7 +170,7 @@ describe('Client', () => {
169170
});
170171

171172
it(`cleanup() - if not connected should do nothing`, async () => {
172-
client = new Client(config);
173+
client = new Client(sessionConfig);
173174
client.ws.send.mockReturnValueOnce(response("cleanupDone", {}, 1));
174175
await client.cleanup();
175176

@@ -215,23 +216,23 @@ describe('Client', () => {
215216
});
216217

217218
it(`execute() - fast invocation should not trigger "slowInvocationStatus"`, async () => {
218-
argparse.getArgValue.mockReturnValue(2); // set debug-slow-invocations
219+
sessionConfig.debugSynchronization = 2;
219220
await connect();
220221
await executeWithSlowInvocation(1);
221222
expect(client.ws.send).toHaveBeenLastCalledWith({"params": {"args": ["test"], "method": "matcherForAccessibilityLabel:", "target": {"type": "Class", "value": "GREYMatchers"}}, "type": "invoke"}, undefined);
222223
expect(client.ws.send).toHaveBeenCalledTimes(2);
223224
});
224225

225226
it(`execute() - slow invocation should trigger "slowInvocationStatus:`, async () => {
226-
argparse.getArgValue.mockReturnValue(2); // set debug-slow-invocations
227+
sessionConfig.debugSynchronization = 2;
227228
await connect();
228229
await executeWithSlowInvocation(3);
229230
expect(client.ws.send).toHaveBeenLastCalledWith({"params": {}, "type": "currentStatus"}, undefined);
230231
expect(client.ws.send).toHaveBeenCalledTimes(3);
231232
});
232233

233234
it(`execute() - slow invocation should do nothing if ws was closed`, async () => {
234-
argparse.getArgValue.mockReturnValue(2); // set debug-slow-invocations
235+
sessionConfig.debugSynchronization = 2;
235236
await connect();
236237
client.ws.isOpen.mockReturnValue(false);
237238
await executeWithSlowInvocation(4);
@@ -242,7 +243,7 @@ describe('Client', () => {
242243
// NOTE: this test prevents geometrical progression of currentStatus calls
243244
// when the device gets really busy. Otherwise, we can get 1000 pending currentStatus calls.
244245
it(`slowInvocationStatus() - should not schedule currentStatus thrice`, async () => {
245-
argparse.getArgValue.mockReturnValue(2); // set debug-slow-invocations
246+
sessionConfig.debugSynchronization = 2;
246247
await connect();
247248

248249
jest.spyOn(client, 'slowInvocationStatus');
@@ -440,7 +441,7 @@ describe('Client', () => {
440441
});
441442

442443
async function connect() {
443-
client = new Client(config);
444+
client = new Client(sessionConfig);
444445
client.ws.send.mockReturnValueOnce(response("loginSuccess", {}, 1));
445446
await client.connect();
446447
client.ws.isOpen.mockReturnValue(true);
Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,52 @@
11
const getPort = require('get-port');
22
const uuid = require('../utils/uuid');
3+
const isValidWebsocketURL = require('../utils/isValidWebsocketURL');
34

45
/**
56
*
67
* @param {DetoxConfigErrorBuilder} errorBuilder
78
* @param {*} detoxConfig
89
* @param {*} deviceConfig
910
*/
10-
async function composeSessionConfig({ errorBuilder, detoxConfig, deviceConfig }) {
11-
const session = deviceConfig.session || detoxConfig.session || {
12-
autoStart: true,
13-
server: `ws://localhost:${await getPort()}`,
14-
sessionId: uuid.UUID(),
11+
async function composeSessionConfig({ errorBuilder, cliConfig, detoxConfig, deviceConfig }) {
12+
const session = {
13+
...detoxConfig.session,
14+
...deviceConfig.session,
1515
};
1616

17-
if (!session.server) {
18-
throw errorBuilder.missingServerProperty();
17+
if (session.server != null) {
18+
const value = session.server;
19+
if (typeof value !== 'string' || !isValidWebsocketURL(value)) {
20+
throw errorBuilder.invalidServerProperty();
21+
}
22+
}
23+
24+
if (session.sessionId != null) {
25+
const value = session.sessionId;
26+
if (typeof value !== 'string' || value.length === 0) {
27+
throw errorBuilder.invalidSessionIdProperty();
28+
}
29+
}
30+
31+
if (session.debugSynchronization != null) {
32+
const value = session.debugSynchronization;
33+
if (typeof value !== 'number' || value < 0) {
34+
throw errorBuilder.invalidDebugSynchronizationProperty();
35+
}
1936
}
2037

21-
if (!session.sessionId) {
22-
throw errorBuilder.missingSessionIdProperty();
38+
if (cliConfig.debugSynchronization > 0) {
39+
session.debugSynchronization = +cliConfig.debugSynchronization;
2340
}
2441

25-
return session;
42+
return {
43+
autoStart: !session.server,
44+
server: `ws://localhost:${await getPort()}`,
45+
sessionId: uuid.UUID(),
46+
debugSynchronization: false,
47+
48+
...session,
49+
};
2650
}
2751

2852
module.exports = composeSessionConfig;

detox/src/configuration/composeSessionConfig.test.js

Lines changed: 163 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,70 +2,201 @@ const DetoxConfigErrorBuilder = require('../errors/DetoxConfigErrorBuilder');
22

33
describe('composeSessionConfig', () => {
44
let composeSessionConfig;
5-
let detoxConfig, deviceConfig;
5+
let cliConfig, detoxConfig, deviceConfig;
66
/** @type {DetoxConfigErrorBuilder} */
77
let errorBuilder;
88

99
beforeEach(() => {
1010
composeSessionConfig = require('./composeSessionConfig');
1111
errorBuilder = new DetoxConfigErrorBuilder();
12+
cliConfig = {};
1213
detoxConfig = {};
1314
deviceConfig = {};
1415
});
1516

1617
const compose = () => composeSessionConfig({
18+
cliConfig,
1719
detoxConfig,
1820
deviceConfig,
1921
errorBuilder,
2022
});
2123

2224
it('should generate a default config', async () => {
23-
const sessionConfig = await compose();
24-
25-
expect(sessionConfig).toMatchObject({
25+
expect(await compose()).toEqual({
2626
autoStart: true,
27-
server: expect.stringMatching(/^ws:.*localhost:/),
28-
sessionId: expect.stringMatching(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i),
27+
debugSynchronization: false,
28+
server: expect.any(String),
29+
sessionId: expect.any(String),
30+
});
31+
});
32+
33+
describe('sessionId', function() {
34+
describe('by default', () => {
35+
it('should be autogenerated GUID', async () => {
36+
expect(await compose()).toMatchObject({
37+
sessionId: expect.stringMatching(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i),
38+
});
39+
});
40+
});
41+
42+
it('should pass validations', async () => {
43+
detoxConfig.session = { sessionId: 1234 };
44+
await expect(compose()).rejects.toThrowError(errorBuilder.invalidSessionIdProperty());
45+
46+
detoxConfig.session = { sessionId: '' };
47+
await expect(compose()).rejects.toThrowError(errorBuilder.invalidSessionIdProperty());
48+
});
49+
50+
describe('when defined in global config', () => {
51+
beforeEach(() => {
52+
detoxConfig.session = { sessionId: 'someSessionId' };
53+
});
54+
55+
it('should use the specified value', async () => {
56+
expect((await compose()).sessionId).toBe('someSessionId');
57+
});
58+
59+
describe('and in device config', () => {
60+
beforeEach(() => {
61+
deviceConfig.session = { sessionId: 'otherSessionId' };
62+
});
63+
64+
it('should use the specified value', async () => {
65+
expect((await compose()).sessionId).toBe('otherSessionId');
66+
});
67+
});
68+
});
69+
});
70+
71+
describe('server', function() {
72+
describe('by default', () => {
73+
it('should be autogenerated', async () => {
74+
expect(await compose()).toMatchObject({
75+
server: expect.stringMatching(/^ws:.*localhost:/),
76+
});
77+
});
78+
});
79+
80+
it('should pass validations', async () => {
81+
detoxConfig.session = { server: 1234 };
82+
await expect(compose()).rejects.toThrowError(errorBuilder.invalidServerProperty());
83+
84+
detoxConfig.session = { server: 'http://invalid-protocol.com' };
85+
await expect(compose()).rejects.toThrowError(errorBuilder.invalidServerProperty());
86+
});
87+
88+
describe('when defined in global config', () => {
89+
beforeEach(() => {
90+
detoxConfig.session = { server: 'ws://myserver:1100' };
91+
});
92+
93+
it('should use the specified value', async () => {
94+
expect(await compose()).toMatchObject({
95+
server: 'ws://myserver:1100',
96+
});
97+
});
98+
99+
describe('and in device config', () => {
100+
beforeEach(() => {
101+
deviceConfig.session = { server: 'ws://otherserver:1100' };
102+
});
103+
104+
it('should use the specified value', async () => {
105+
expect(await compose()).toMatchObject({
106+
server: 'ws://otherserver:1100',
107+
});
108+
});
109+
});
29110
});
30111
});
31112

32-
describe('if detoxConfig.session is defined', function() {
33-
beforeEach(() => {
34-
detoxConfig.session = {
35-
server: 'ws://localhost:9999',
36-
sessionId: 'someSessionId',
37-
};
38-
})
113+
describe('autoStart', function() {
114+
describe('by default', () => {
115+
it('should be true', async () => {
116+
expect(await compose()).toMatchObject({ autoStart: true });
117+
});
118+
});
119+
120+
describe('when autoStart is explicitly false', function() {
121+
beforeEach(() => {
122+
detoxConfig.session = { autoStart: false };
123+
});
124+
125+
it('should override the value', async () => {
126+
expect(await compose()).toMatchObject({ autoStart: false });
127+
});
128+
});
129+
130+
describe('when server is defined', () => {
131+
beforeEach(() => {
132+
detoxConfig.session = { server: 'ws://localhost:1100' };
133+
});
134+
135+
it('should be false', async () => {
136+
expect(await compose()).toMatchObject({ autoStart: false });
137+
});
39138

40-
it('should return detoxConfig.session', async () => {
41-
expect(await compose()).toEqual({
42-
server: 'ws://localhost:9999',
43-
sessionId: 'someSessionId',
139+
describe('when autoStart is explicitly true', function() {
140+
beforeEach(() => {
141+
detoxConfig.session.autoStart = true;
142+
});
143+
144+
it('should override the value', async () => {
145+
expect(await compose()).toMatchObject({ autoStart: true });
146+
});
44147
});
45148
});
149+
});
46150

47-
test(`providing empty server config should throw`, () => {
48-
delete detoxConfig.session.server;
49-
expect(compose()).rejects.toThrowError(errorBuilder.missingServerProperty());
151+
describe('debugSynchronization', function () {
152+
describe('by default', () => {
153+
it('should be false', async () => {
154+
expect(await compose()).toMatchObject({
155+
debugSynchronization: false,
156+
});
157+
});
50158
});
51159

52-
test(`providing server config with no session should throw`, () => {
53-
delete detoxConfig.session.sessionId;
54-
expect(compose()).rejects.toThrowError(errorBuilder.missingSessionIdProperty());
160+
it('should pass validations', async () => {
161+
detoxConfig.session = { debugSynchronization: -1 };
162+
await expect(compose()).rejects.toThrowError(errorBuilder.invalidDebugSynchronizationProperty());
163+
164+
detoxConfig.session = { debugSynchronization: '3000' };
165+
await expect(compose()).rejects.toThrowError(errorBuilder.invalidDebugSynchronizationProperty());
55166
});
56167

57-
describe('if deviceConfig.session is defined', function() {
168+
describe('when defined in global config', () => {
58169
beforeEach(() => {
59-
detoxConfig.session = {
60-
server: 'ws://localhost:1111',
61-
sessionId: 'anotherSession',
62-
};
170+
detoxConfig.session = { debugSynchronization: 10000 };
63171
});
64172

65-
it('should return deviceConfig.session instead of detoxConfig.session', async () => {
66-
expect(await compose()).toEqual({
67-
server: 'ws://localhost:1111',
68-
sessionId: 'anotherSession',
173+
it('should use that value', async () => {
174+
expect(await compose()).toMatchObject({
175+
debugSynchronization: 10000,
176+
});
177+
});
178+
179+
describe('and in device config', () => {
180+
beforeEach(() => {
181+
deviceConfig.session = { debugSynchronization: 20000 };
182+
});
183+
184+
it('should use that value', async () => {
185+
expect(await compose()).toMatchObject({
186+
debugSynchronization: 20000,
187+
});
188+
});
189+
190+
describe('and in CLI config', () => {
191+
beforeEach(() => {
192+
cliConfig.debugSynchronization = 3000;
193+
});
194+
195+
it('should use that value', async () => {
196+
expect(await compose()).toMatchObject({
197+
debugSynchronization: 3000,
198+
});
199+
});
69200
});
70201
});
71202
});

0 commit comments

Comments
 (0)