detox 20.0.6-breaking.new-global-lifecycle.0 → 20.0.9-prerelease.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. package/Detox-android/com/wix/detox/{20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0-javadoc.jar → 20.0.9-prerelease.0/detox-20.0.9-prerelease.0-javadoc.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0-javadoc.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0-javadoc.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0-javadoc.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0-javadoc.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0-sources.jar → 20.0.9-prerelease.0/detox-20.0.9-prerelease.0-sources.jar} +0 -0
  7. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0-sources.jar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0-sources.jar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0-sources.jar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0-sources.jar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0.aar +0 -0
  12. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0.aar.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0.aar.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0.aar.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0.aar.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/{20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0.pom → 20.0.9-prerelease.0/detox-20.0.9-prerelease.0.pom} +1 -7
  17. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0.pom.md5 +1 -0
  18. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0.pom.sha1 +1 -0
  19. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0.pom.sha256 +1 -0
  20. package/Detox-android/com/wix/detox/20.0.9-prerelease.0/detox-20.0.9-prerelease.0.pom.sha512 +1 -0
  21. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  22. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  23. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  24. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  25. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  26. package/Detox-ios-src.tbz +0 -0
  27. package/Detox-ios.tbz +0 -0
  28. package/android/build.gradle +12 -6
  29. package/android/detox/build.gradle +13 -9
  30. package/android/detox/proguard-rules-app.pro +4 -0
  31. package/android/detox/publishing.gradle +27 -27
  32. package/android/detox/src/full/java/com/wix/detox/DetoxCrashHandler.kt +1 -1
  33. package/android/detox/src/full/java/com/wix/detox/LaunchArgs.java +9 -0
  34. package/android/detox/src/full/java/com/wix/detox/TestEngineFacade.kt +1 -1
  35. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeExtension.kt +15 -2
  36. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeIdlingResources.kt +43 -38
  37. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategy.kt +7 -27
  38. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/IdleInterrogationStrategy.kt +1 -11
  39. package/android/detox/src/main/java/com/wix/detox/common/TextFileReader.kt +1 -1
  40. package/android/detox/src/testFull/java/com/wix/detox/espresso/action/DetoxMultiTapSpec.kt +4 -3
  41. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategySpec.kt +3 -11
  42. package/index.d.ts +21 -1
  43. package/internals.d.ts +11 -0
  44. package/local-cli/test.js +9 -4
  45. package/local-cli/testCommand/TestRunnerCommand.js +2 -3
  46. package/package.json +3 -3
  47. package/runners/jest/testEnvironment/index.js +3 -2
  48. package/src/DetoxWorker.js +0 -6
  49. package/src/android/core/NativeElement.js +56 -20
  50. package/src/android/core/NativeExpect.js +28 -9
  51. package/src/android/interactions/native.js +24 -18
  52. package/src/artifacts/timeline/TimelineContextTypes.js +7 -0
  53. package/src/client/Client.js +18 -1
  54. package/src/configuration/composeRunnerConfig.js +1 -0
  55. package/src/devices/common/drivers/android/tools/MonitoredInstrumentation.js +1 -1
  56. package/src/devices/common/drivers/ios/tools/AppleSimUtils.js +3 -1
  57. package/src/devices/runtime/RuntimeDevice.js +2 -4
  58. package/src/devices/runtime/drivers/android/AndroidDriver.js +9 -1
  59. package/src/ios/expectTwo.js +152 -67
  60. package/src/logger/DetoxLogger.js +5 -1
  61. package/src/{utils → logger/utils}/streamUtils.js +28 -1
  62. package/src/logger/utils/tracerLegacy.js +24 -1
  63. package/src/realms/DetoxContext.js +8 -0
  64. package/src/realms/DetoxInternalsFacade.js +1 -1
  65. package/src/realms/DetoxPrimaryContext.js +9 -10
  66. package/src/symbols.js +2 -0
  67. package/src/utils/errorUtils.js +4 -3
  68. package/src/utils/invocationTraceDescriptions.js +43 -0
  69. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0-javadoc.jar.md5 +0 -1
  70. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0-javadoc.jar.sha1 +0 -1
  71. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0-javadoc.jar.sha256 +0 -1
  72. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0-javadoc.jar.sha512 +0 -1
  73. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0-sources.jar.md5 +0 -1
  74. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0-sources.jar.sha1 +0 -1
  75. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0-sources.jar.sha256 +0 -1
  76. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0-sources.jar.sha512 +0 -1
  77. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0.aar +0 -0
  78. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0.aar.md5 +0 -1
  79. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0.aar.sha1 +0 -1
  80. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0.aar.sha256 +0 -1
  81. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0.aar.sha512 +0 -1
  82. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0.pom.md5 +0 -1
  83. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0.pom.sha1 +0 -1
  84. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0.pom.sha256 +0 -1
  85. package/Detox-android/com/wix/detox/20.0.6-breaking.new-global-lifecycle.0/detox-20.0.6-breaking.new-global-lifecycle.0.pom.sha512 +0 -1
  86. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/DefaultIdleInterrogationStrategy.kt +0 -84
  87. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/DefaultIdleInterrogationStrategySpec.kt +0 -115
@@ -2,13 +2,16 @@ const { PassThrough, Transform } = require('stream');
2
2
 
3
3
  const bunyanDebugStream = require('bunyan-debug-stream');
4
4
  const duplexify = require('duplexify');
5
+ const fs = require('fs-extra');
6
+ const glob = require('glob');
5
7
  const multiSort = require('multi-sort-stream');
6
8
  const pipe = require('multipipe');
7
9
  const JsonlParser = require('stream-json/jsonl/Parser');
8
10
  const stripAnsi = require('strip-ansi');
9
11
  const { AbstractEventBuilder } = require('trace-event-lib');
10
12
 
11
- const log = require('./logger').child({ cat: 'logger' });
13
+ const temporary = require('../../artifacts/utils/temporaryPath');
14
+ const log = require('../../utils/logger').child({ cat: 'logger' });
12
15
 
13
16
  function compareTimestamps(a, b) {
14
17
  return +(a.value.time > b.value.time) - +(a.value.time < b.value.time);
@@ -203,6 +206,29 @@ function readJSONL() {
203
206
  return duplexify.obj(preventErrorSubscriptions(writable), writable.pipe(readable));
204
207
  }
205
208
 
209
+ /**
210
+ * @param {string} sessionId
211
+ * @returns {NodeJS.ReadableStream}
212
+ */
213
+ function uniteSessionLogs(sessionId) {
214
+ const readable = through();
215
+
216
+ glob(temporary.for.jsonl(`${sessionId}.*`), function (err, logs) {
217
+ if (err) {
218
+ return readable.emit('error', err);
219
+ }
220
+
221
+ if (logs.length === 0) {
222
+ return;
223
+ }
224
+
225
+ const jsonlStreams = logs.map(filePath => fs.createReadStream(filePath).pipe(readJSONL()));
226
+ mergeSortedJSONL(jsonlStreams).pipe(readable);
227
+ });
228
+
229
+ return readable;
230
+ }
231
+
206
232
  module.exports = {
207
233
  readJSONL,
208
234
  writeJSON,
@@ -210,4 +236,5 @@ module.exports = {
210
236
  mergeSortedJSONL,
211
237
  debugStream,
212
238
  chromeTraceStream,
239
+ uniteSessionLogs,
213
240
  };
@@ -8,15 +8,38 @@ const methods = {
8
8
  },
9
9
 
10
10
  traceCall(logger) {
11
- return (name, action) => logger.trace.complete(name, action);
11
+ return (name, action, args = {}) => logger.trace.complete(args, name, action);
12
+ },
13
+
14
+ invocationCall(logger) {
15
+ return (sectionName, invocation, action) => {
16
+ return logger.trace.complete({
17
+ cat: 'ws-client,ws-client-invocation',
18
+ data: invocation,
19
+ stack: _getCallStackTrace(),
20
+ }, sectionName, action);
21
+ };
12
22
  },
13
23
  };
14
24
 
25
+ function _getCallStackTrace() {
26
+ return new Error().stack
27
+ .split('\n')
28
+ .slice(1) // Ignore Error message
29
+ .map(line => line
30
+ .replace(/^\s*at\s+/, '')
31
+ .replace(process.cwd(), '')
32
+ )
33
+ .filter(line => !line.includes('/detox/src')) // Ignore detox internal calls
34
+ .join('\n');
35
+ }
36
+
15
37
  function installLegacyTracerInterface(logger, target) {
16
38
  target.traceCall = methods.traceCall(logger);
17
39
  target.trace = Object.freeze({
18
40
  startSection: methods.startSection(logger),
19
41
  endSection: methods.endSection(logger),
42
+ invocationCall: methods.invocationCall(logger),
20
43
  });
21
44
 
22
45
  return this;
@@ -94,6 +94,14 @@ class DetoxContext {
94
94
  [symbols.onRunFinish] = (...args) => this[symbols.worker].onRunFinish(...args);
95
95
  [symbols.config] = funpermaproxy(() => this[symbols.session].detoxConfig);
96
96
  [symbols.session] = funpermaproxy(() => this[$sessionState]);
97
+ [symbols.tracing] = Object.freeze({
98
+ createEventStream: () => {
99
+ const streamUtils = require('../logger/utils/streamUtils');
100
+ return streamUtils
101
+ .uniteSessionLogs(this[$sessionState].id)
102
+ .pipe(streamUtils.chromeTraceStream());
103
+ },
104
+ });
97
105
  /** @abstract */
98
106
  [symbols.reportFailedTests](_testFilePaths, _permanent) {}
99
107
  /**
@@ -29,7 +29,7 @@ class DetoxInternalsFacade {
29
29
  this.reportFailedTests = context[symbols.reportFailedTests];
30
30
  this.resolveConfig = context[symbols.resolveConfig];
31
31
  this.session = context[symbols.session];
32
- this.trace = context.trace;
32
+ this.tracing = context[symbols.tracing];
33
33
  this.worker = funpermaproxy(() => context[symbols.worker]);
34
34
  }
35
35
  }
@@ -170,11 +170,13 @@ class DetoxPrimaryContext extends DetoxContext {
170
170
 
171
171
  await fs.remove(this[$sessionState].detoxConfigSnapshotPath);
172
172
 
173
- try {
174
- this[_lifecycleLogger].trace.end();
175
- await this[_finalizeLogs]();
176
- } catch (err) {
177
- this[_lifecycleLogger].error({ err }, 'Encountered an error while merging the process logs:');
173
+ if (this[_dirty]) {
174
+ try {
175
+ this[_lifecycleLogger].trace.end();
176
+ await this[_finalizeLogs]();
177
+ } catch (err) {
178
+ this[_lifecycleLogger].error({ err }, 'Encountered an error while merging the process logs:');
179
+ }
178
180
  }
179
181
  }
180
182
  }
@@ -230,17 +232,14 @@ class DetoxPrimaryContext extends DetoxContext {
230
232
  }
231
233
 
232
234
  if (this[_areLogsEnabled]()) {
233
- const streamUtils = require('../utils/streamUtils');
235
+ const streamUtils = require('../logger/utils/streamUtils');
234
236
  const { rootDir } = this[symbols.config].artifacts;
235
237
 
236
238
  await fs.mkdirp(rootDir);
237
239
  const [out1Stream, out2Stream, out3Stream] = ['detox.log.jsonl', 'detox.log', 'detox.trace.json']
238
240
  .map((filename) => fs.createWriteStream(path.join(rootDir, filename)));
239
241
 
240
- const mergedStream = streamUtils
241
- .mergeSortedJSONL(
242
- logs.map(filePath => fs.createReadStream(filePath).pipe(streamUtils.readJSONL()))
243
- );
242
+ const mergedStream = streamUtils.uniteSessionLogs(sessionId);
244
243
 
245
244
  await Promise.all([
246
245
  pipe(mergedStream, streamUtils.writeJSONL(), out1Stream),
package/src/symbols.js CHANGED
@@ -21,6 +21,7 @@
21
21
  * readonly reportFailedTests: unique symbol;
22
22
  * readonly resolveConfig: unique symbol;
23
23
  * readonly session: unique symbol;
24
+ * readonly tracing: unique symbol;
24
25
  * readonly uninstallWorker: unique symbol;
25
26
  * readonly worker: unique symbol;
26
27
  * }}
@@ -54,6 +55,7 @@ module.exports = {
54
55
  logger: Symbol('logger'),
55
56
  resolveConfig: Symbol('resolveConfig'),
56
57
  session: Symbol('session'),
58
+ tracing: Symbol('tracing'),
57
59
  uninstallWorker: Symbol('uninstallWorker'),
58
60
  worker: Symbol('worker'),
59
61
  //#endregion
@@ -16,11 +16,12 @@ function filterErrorStack(error, predicate) {
16
16
  }
17
17
 
18
18
  function replaceErrorStack(source, target) {
19
- const sourceStack = source.stack || source.message;
20
- const targetStack = target.stack || target.message;
19
+ const sourceStack = (source.stack || '');
21
20
  const sourceMessage = sourceStack.replace(CLEAN_AT, '');
22
- const targetMessage = targetStack.replace(CLEAN_AT, '');
23
21
  const actualSourceStack = sourceStack.slice(sourceMessage.length);
22
+
23
+ const targetMessage = target.message || target.stack.replace(CLEAN_AT, '');
24
+
24
25
  target.stack = targetMessage + actualSourceStack;
25
26
  return target;
26
27
  }
@@ -0,0 +1,43 @@
1
+ module.exports = {
2
+ actionDescription: {
3
+ adjustSliderToPosition: (newPosition) => `adjust slider to position ${newPosition}`,
4
+ clearText: () => 'clear input text',
5
+ getAttributes: () => 'get element attributes',
6
+ longPress: (duration) => `long press${duration !== undefined ? ` for ${duration}ms` : ''}`,
7
+ longPressAndDrag: (duration, startX, startY, targetElement, endX, endY, speed, holdDuration) =>
8
+ `long press and drag from ${startX}, ${startY} to ${endX}, ${endY} with speed ${speed} and hold duration ${holdDuration}`,
9
+ multiTap: (times) => `tap ${times} times`,
10
+ pinch: (scale, speed, angle) => `pinch with scale ${scale}, speed ${speed}, and angle ${angle}`,
11
+ pinchWithAngle: (direction, speed, angle) => `pinch with direction ${direction}, speed ${speed}, and angle ${angle}`,
12
+ replaceText: (value) => `replace input text: "${value}"`,
13
+ scroll: (amount, direction, startPositionX, startPositionY) =>
14
+ `scroll ${amount} pixels ${direction}${startPositionX !== undefined && startPositionY !== undefined ? ` from normalized position (${startPositionX}, ${startPositionY})` : ''}`,
15
+ scrollTo: (edge) => `scroll to ${edge}`,
16
+ scrollToIndex: (index) => `scroll to index #${index}`,
17
+ setColumnToValue: (column, value) => `set column ${column} to value ${value}`,
18
+ setDatePickerDate: (dateString, dateFormat) => `set date picker date to ${dateString} using format ${dateFormat}`,
19
+ swipe: (direction, speed, normalizedSwipeOffset, normalizedStartingPointX, normalizedStartingPointY) =>
20
+ `swipe ${direction} ${speed} with offset ${normalizedSwipeOffset}
21
+ ${!isNaN(normalizedStartingPointX) && !isNaN(normalizedStartingPointY) ? ` from normalized position (${normalizedStartingPointX}, ${normalizedStartingPointY})` : ''}`,
22
+ takeScreenshot: (screenshotName) => `take screenshot${screenshotName !== undefined ? ` with name "${screenshotName}"` : ''}`,
23
+ tapAtPoint: (value) => `tap${value !== undefined ? ` at ${JSON.stringify(value)}` : ''}`,
24
+ tapBackspaceKey: () => 'tap on backspace key',
25
+ tapReturnKey: () => 'tap on return key',
26
+ typeText: (value) => `type input text: "${value}"`,
27
+ },
28
+ expectDescription: {
29
+ waitFor: (actionDescription) => `wait for expectation while ${actionDescription}`,
30
+ waitForWithTimeout: (expectDescription, timeout) => `${expectDescription} with timeout (${timeout} ms)`,
31
+ withTimeout: (timeout) => `wait until timeout (${timeout} ms)`,
32
+ toBeFocused: () => 'to be focused',
33
+ toBeVisible: (percent) => `to be visible${percent !== undefined ? ` ${percent}%` : ''}`,
34
+ toExist: () => 'to exist',
35
+ toHaveText: (text) => `to have text: "${text}"`,
36
+ toHaveLabel: (label) => `to have label: "${label}"`,
37
+ toHaveId: (id) => `to have id: "${id}"`,
38
+ toHaveValue: (value) => `to have value: "${value}"`,
39
+ toHaveSliderPosition: (position, tolerance) => `to have slider position: ${position}${tolerance > 0 ? ` with tolerance ${tolerance}` : ''}`,
40
+ toHaveToggleValue: (value) => `to have toggle value: ${value}`,
41
+ full: (expectDescription, notCondition) => `expect element ${notCondition ? `not ${expectDescription}` : expectDescription}`
42
+ }
43
+ };
@@ -1 +0,0 @@
1
- c78417c3025b0c430f0270d630bd8cc3d680cbd413481ce57f63f44f682fa734
@@ -1 +0,0 @@
1
- 2ed2683d4799f88018b813696666c11fceba5be25b261a780fba9b4f85fa52518577315762760f2c5898bb75dfd8b9f7141a4fe276f17dbf431f4db2f8972b62
@@ -1 +0,0 @@
1
- 88cf8c98c0342ba8052e6483f32c0b21c55e8b49e3e821a8ab7f4d1241f3ac95
@@ -1 +0,0 @@
1
- 086ec58c930ddd33b055906bc7baeab54bf0c61e824168e714542b3c12435765ea79207f73084cf9f865d5afd4d0d277454bad7df260d91904fc839068ba3345
@@ -1 +0,0 @@
1
- 7828ffdff493f8a856efad9b7db540e4e6b13b91260292efe98791013ba2db04
@@ -1 +0,0 @@
1
- a57448b5eec5edfb2b45b99b9c8a6714b3384f71ade0f25da7b453d14c29597e818690b293cefa9be9362be29efb2c420664283cb1fc994883cd143d46506a2f
@@ -1 +0,0 @@
1
- 33f28069a49462150368c6c44ea5644083c155771aefdd51f0e41858dc43e61d
@@ -1 +0,0 @@
1
- 6d57cb6061f616f09785d65238df8c1bb4751a95e65f1dd038b4c4b7d7b72a5d6572fc49ee4085c26a44407219a8c952e46263457fee030a17b3767654557da6
@@ -1,84 +0,0 @@
1
- @file:RNDropSupportTodo(62, "Remove all of this; Use DelegatedIdleInterrogationStrategy, instead.")
2
-
3
- package com.wix.detox.reactnative.idlingresources.timers
4
-
5
- import com.facebook.react.bridge.ReactContext
6
- import com.wix.detox.common.RNDropSupportTodo
7
- import com.wix.detox.reactnative.helpers.RNHelpers
8
- import org.joor.Reflect
9
- import java.util.*
10
-
11
- private const val BUSY_WINDOW_THRESHOLD = 1500
12
-
13
- private class TimerReflected(timer: Any) {
14
- private var reflected = Reflect.on(timer)
15
-
16
- val isRepeating: Boolean
17
- get() = reflected.field("mRepeat").get()
18
- val interval: Int
19
- get() = reflected.field("mInterval").get()
20
- val targetTime: Long
21
- get() = reflected.field("mTargetTime").get()
22
- }
23
-
24
- private class TimingModuleReflected(private val timingModule: Any) {
25
- val timersQueue: PriorityQueue<Any>
26
- get() = Reflect.on(timingModule).field("mTimers").get()
27
- val timersLock: Any
28
- get() = Reflect.on(timingModule).field("mTimerGuard").get()
29
-
30
- operator fun component1() = timersQueue
31
- operator fun component2() = timersLock
32
- }
33
-
34
- class DefaultIdleInterrogationStrategy
35
- internal constructor(private val timersModule: Any)
36
- : IdleInterrogationStrategy {
37
-
38
- override fun isIdleNow(): Boolean {
39
- val (timersQueue, timersLock) = TimingModuleReflected(timersModule)
40
- synchronized(timersLock) {
41
- val nextTimer = timersQueue.peek()
42
- nextTimer?.let {
43
- return !isTimerInBusyWindow(it) && !hasBusyTimers(timersQueue)
44
- }
45
- return true
46
- }
47
- }
48
-
49
- private fun isTimerInBusyWindow(timer: Any): Boolean {
50
- val timerReflected = TimerReflected(timer)
51
- return when {
52
- timerReflected.isRepeating -> false
53
- timerReflected.interval > BUSY_WINDOW_THRESHOLD -> false
54
- else -> true
55
- }
56
- }
57
-
58
- private fun hasBusyTimers(timersQueue: PriorityQueue<Any>): Boolean {
59
- timersQueue.forEach {
60
- if (isTimerInBusyWindow(it)) {
61
- return true
62
- }
63
- }
64
- return false
65
- }
66
-
67
- companion object {
68
- fun createIfSupported(reactContext: ReactContext): DefaultIdleInterrogationStrategy? {
69
- // RN = 0.62.0:
70
- // Should have been handled by DelegatedIdleInterrogationStrategy.createIfSupported() but seems the new TimingModule class
71
- // was released without the awaited-for "hasActiveTimersInRange()" method.
72
- try {
73
- val timingModule = RNHelpers.getNativeModule(reactContext, "com.facebook.react.modules.core.TimingModule")
74
- val timersManager = Reflect.on(timingModule).get<Any>("mJavaTimerManager")
75
- return DefaultIdleInterrogationStrategy(timersManager)
76
- } catch (ex: Exception) {
77
- }
78
-
79
- // RN < 0.62
80
- val timingModule = RNHelpers.getNativeModule(reactContext, "com.facebook.react.modules.core.Timing") ?: return null
81
- return DefaultIdleInterrogationStrategy(timingModule)
82
- }
83
- }
84
- }
@@ -1,115 +0,0 @@
1
- package com.wix.detox.reactnative.idlingresources.timers
2
-
3
- import com.facebook.react.bridge.NativeModule
4
- import com.wix.detox.UTHelpers
5
- import org.assertj.core.api.Assertions
6
- import org.spekframework.spek2.Spek
7
- import org.spekframework.spek2.style.specification.describe
8
- import java.util.*
9
- import java.util.concurrent.Executors
10
-
11
- private const val BUSY_INTERVAL_MS = 1500
12
- private const val MEANINGFUL_TIMER_INTERVAL = BUSY_INTERVAL_MS
13
-
14
- data class TimerStub(
15
- private var mCallbackID: Int,
16
- private var mTargetTime: Long,
17
- private var mInterval: Int,
18
- private var mRepeat: Boolean)
19
-
20
- class TimersNativeModuleStub : NativeModule {
21
- val mTimers: PriorityQueue<Any> = PriorityQueue(2) { _, _ -> 0}
22
- val mTimerGuard = "Lock-Mock"
23
-
24
- override fun onCatalystInstanceDestroy() {}
25
- override fun invalidate() {}
26
-
27
- override fun getName(): String = "TimersNativeModuleStub"
28
- override fun canOverrideExistingModule() = false
29
- override fun initialize() {}
30
- }
31
-
32
- private fun now() = System.nanoTime() / 1000000L
33
- private fun aTimer(targetTime: Long, interval: Int, isRepeating: Boolean): TimerStub {
34
- return TimerStub(-1, targetTime, interval, isRepeating)
35
- }
36
- private fun aTimer(interval: Int, isRepeating: Boolean) = aTimer(now() + interval + 10, interval, isRepeating)
37
- private fun aOneShotTimer(interval: Int) = aTimer(interval, false)
38
- private fun aRepeatingTimer(interval: Int) = aTimer(interval, true)
39
- private fun anOverdueTimer() = aTimer(now() - 100, 123, false)
40
-
41
- object DefaultIdleInterrogationStrategySpec: Spek({
42
- describe("Default timers idle-interrogation strategy") {
43
-
44
- lateinit var timersNativeModule: TimersNativeModuleStub
45
-
46
- beforeEachTest {
47
- timersNativeModule = TimersNativeModuleStub()
48
- }
49
-
50
- fun givenTimer(timer: Any) {
51
- timersNativeModule.mTimers.add(timer)
52
- }
53
-
54
- fun uut() = DefaultIdleInterrogationStrategy(timersNativeModule)
55
-
56
- it("should be idle if there are no timers in queue") {
57
- Assertions.assertThat(uut().isIdleNow()).isTrue()
58
- }
59
-
60
- it("should be busy if there's a meaningful pending timer") {
61
- givenTimer(aOneShotTimer(MEANINGFUL_TIMER_INTERVAL))
62
- Assertions.assertThat(uut().isIdleNow()).isFalse()
63
- }
64
-
65
- it("should be idle if pending timer is too far away (ie not meaningful)") {
66
- givenTimer(aOneShotTimer(BUSY_INTERVAL_MS + 1))
67
- Assertions.assertThat(uut().isIdleNow()).isTrue()
68
- }
69
-
70
- it("should be idle if the only timer is a repeating one") {
71
- givenTimer(aRepeatingTimer(MEANINGFUL_TIMER_INTERVAL))
72
- Assertions.assertThat(uut().isIdleNow()).isTrue()
73
- }
74
-
75
- it("should be busy if a meaningful pending timer lies beyond a repeating one") {
76
- givenTimer(aRepeatingTimer(BUSY_INTERVAL_MS / 10))
77
- givenTimer(aOneShotTimer(BUSY_INTERVAL_MS))
78
- Assertions.assertThat(uut().isIdleNow()).isFalse()
79
- }
80
-
81
- /**
82
- * Note: Reversed logic due to this issue: https://github.com/wix/Detox/issues/1171 !!!
83
- *
84
- * Apparently at times (rare) this caused Espresso to think we're idle too soon, rendering
85
- * it never to query any idling resource again even after the timer effectively expires...
86
- */
87
- it("should be *busy* even if all timers are overdue") {
88
- givenTimer(anOverdueTimer())
89
- givenTimer(anOverdueTimer())
90
- Assertions.assertThat(uut().isIdleNow()).isFalse()
91
- }
92
-
93
- it("should be busy if has a meaningful pending timer set beyond an overdue timer") {
94
- givenTimer(anOverdueTimer())
95
- givenTimer(aOneShotTimer(MEANINGFUL_TIMER_INTERVAL))
96
- Assertions.assertThat(uut().isIdleNow()).isFalse()
97
- }
98
-
99
- it("should yield to other threads using the timers module") {
100
- val executor = Executors.newSingleThreadExecutor()
101
- var isIdle: Boolean? = null
102
-
103
- synchronized(timersNativeModule.mTimerGuard) {
104
- executor.submit {
105
- isIdle = uut().isIdleNow()
106
- }
107
- UTHelpers.yieldToOtherThreads(executor)
108
- Assertions.assertThat(isIdle).isNull()
109
- }
110
- UTHelpers.yieldToOtherThreads(executor)
111
- Assertions.assertThat(isIdle).isNotNull()
112
- executor.shutdownNow()
113
- }
114
- }
115
- })