@suffolklitlab/alkiln 5.15.1 → 5.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -45,6 +45,12 @@ Format:
45
45
 
46
46
  <!-- ## [Unreleased] -->
47
47
 
48
+ ## [5.15.2] - 2025-11-29
49
+
50
+ ### Fixed
51
+
52
+ - Fixed race condition between navigating and extracting button text.
53
+
48
54
  ## [5.15.1] - 2025-08-05
49
55
 
50
56
  ### Changed
package/lib/scope.js CHANGED
@@ -254,23 +254,21 @@ module.exports = {
254
254
  if ( options.waitForTimeout ) { await time.waitForTimeout( options.waitForTimeout ); }
255
255
  }, // Ends afterStep
256
256
 
257
- tapElementAndNavigate: async function(scope, { elem, waitForTimeout=0 }) {
258
- /** Tap a given element, detect navigation, and do appropriate related things.
259
- * TODO: not quite sure if we'll be using `waitForTimeout`. */
257
+ tapElementAndNavigate: async function(scope, { elem, waitForTimeout=null }) {
258
+ /** Tap a given element, detect navigation, and do appropriate related things. */
260
259
 
261
260
  let start_url = await scope.page.url(); // so we can later check for navigation
262
261
 
263
262
  await scope.tapElementAndStayOnPage(scope, {
264
263
  elem,
265
- click_promise: Promise.all([
266
- scope.page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
267
- elem.evaluate( (el) => { return el.click(); })
268
- ])
264
+ promise_returners: [
265
+ () => { return scope.nav_race(scope, {}).result; },
266
+ () => { return elem.evaluate( (el) => { return el.click(); }) },
267
+ ]
269
268
  });
270
269
 
271
- // TODO: Not sure how to detect navigation landing at same page, such as
272
- // with a user input validation error. Does that count as navigating?
273
- // Maybe url change + trigger var change? Maybe just trigger var?
270
+ // For other ideas for detecting navigation, see
271
+ // https://github.com/SuffolkLITLab/ALKiln/issues/229
274
272
  let end_url = await scope.page.url();
275
273
  let navigated = start_url !== end_url;
276
274
 
@@ -282,36 +280,77 @@ module.exports = {
282
280
  let { question_has_id, id, id_matches } = await scope.examinePageID( scope, 'none to match' );
283
281
 
284
282
  // If navigation, wait for 200ms to let things finish loading and moving
285
- await scope.afterStep(scope, { waitForTimeout: 200, navigated: true });
283
+ await scope.afterStep(scope, {
284
+ waitForTimeout: ( waitForTimeout || 200 ),
285
+ navigated: true
286
+ });
286
287
  } else {
287
- await scope.afterStep(scope, { waitForTimeout, navigated: false });
288
+ // Discuss: Fail here or later since the test didn't manage to navigate?
289
+ await scope.afterStep(scope, {
290
+ waitForTimeout: ( waitForTimeout || 0 ),
291
+ navigated: false
292
+ });
288
293
  }
289
294
  }, // Ends scope.tapElementAndNavigate()
290
295
 
291
- tapElementAndStayOnPage: async function(scope, { elem, click_promise }) {
296
+ nav_race: function(scope, opts={}) {
297
+ /**
298
+ * Return a Promise for a race that detects which, if any, navigation result
299
+ * a navigation had.
300
+ *
301
+ * Warning: Unable to effectively add event listener for `daPageLoad`. Gets
302
+ * overwritten on every page load.
303
+ *
304
+ * @returns
305
+ * { obj }
306
+ * ok { bool } - True if no unexpected errors
307
+ * messages {[ str ]} - Any error or info messages
308
+ * errors {[ Error ]} - Any Errors that happened
309
+ * result { Promise } with result of { obj } - HTTP response or other data
310
+ * aborters {[ obj ]} - Abort signals for all the Promises involved
311
+ * */
312
+ let ok, messages, errors;
313
+ let aborters = [];
314
+
315
+ let result = Promise.race([
316
+ scope.page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
317
+ ]);
318
+
319
+ return { result, aborters, ok, messages, errors, };
320
+ }, // Ends scope.nav_race()
321
+
322
+ tapElementAndStayOnPage: async function(scope, { elem, promise_returners=null }) {
292
323
  /** Tap a given element, detect navigation, and do appropriate related things.
293
- * `click_promise can be any kind of Promise, including a Promise.all()
324
+ *
325
+ * @param elem { obj } - element to tap
326
+ * @param promise_returners {[ func -> Promise ]} - list of functions that
327
+ * return promises
294
328
  */
295
329
  await scope.guard_against_missing_tap_element(scope, { elem });
296
330
 
297
331
  // Get the text on the button or link for a potential error message later
298
332
  let tapperText = await (await elem.getProperty('textContent')).jsonValue();
299
333
 
300
- click_promise = click_promise || elem.click();
334
+ let click_promises = null;
335
+ if ( promise_returners ) {
336
+ click_promises = Promise.all(promise_returners.map( pr_ret => pr_ret() ));
337
+ } else {
338
+ click_promises = elem.click();
339
+ }
301
340
 
302
341
  // TODO: implement and use scope.handle_possible_timeout()
303
342
  try {
304
- // Serever error loads page, so keeping things shorter by not looking
343
+ // Server error loads page, so keeping things shorter by not looking
305
344
  // for that text too. Also, puppeteer will ensure proper timeout.
306
345
  await Promise.race([
307
- click_promise,
346
+ click_promises,
308
347
  // This is meant to detect user-visible alerts/errors,
309
348
  // not breaking errors in the testing code.
349
+ // No need for aborters since puppeteer has its own timeout
310
350
  scope.page.waitForSelector('.alert-danger', { visible: true }),
311
351
  scope.page.waitForSelector('.da-has-error', { visible: true }),
312
352
  ]);
313
353
  } catch ( error ) {
314
-
315
354
  if ( error.name === `TimeoutError` ) {
316
355
  let non_reload_report_msg = `After ALKiln tapped "${ tapperText }" it took too long to load the next page.`;
317
356
  await scope.handle_page_timeout_error( scope, { non_reload_report_data: {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@suffolklitlab/alkiln",
3
- "version": "5.15.0-deps-2",
3
+ "version": "5.15.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@suffolklitlab/alkiln",
9
- "version": "5.15.0-deps-2",
9
+ "version": "5.15.1",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@axe-core/puppeteer": "4.7.3",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suffolklitlab/alkiln",
3
- "version": "5.15.1",
3
+ "version": "5.15.2",
4
4
  "description": "Integrated automated end-to-end testing with docassemble, puppeteer, and cucumber.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {