@suffolklitlab/alkiln 5.15.0 → 5.15.1-fix-race-condition

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.
@@ -160,7 +160,7 @@ jobs:
160
160
  #### Developer note: You can probably leave the rest out
161
161
  #### To learn more, see https://assemblyline.suffolklitlab.org/docs/alkiln/writing/#optional-inputs
162
162
  ALKILN_TAG_EXPRESSION: "${{ env.ALKILN_TAG_EXPRESSION }}"
163
- # ALKILN_VERSION:
163
+ ALKILN_VERSION: "@race"
164
164
 
165
165
  #### Developer note: Example of making an issue when tests fail
166
166
  #### that includes the text of the failure output file
@@ -82,4 +82,4 @@ jobs:
82
82
  # want to check up on this.
83
83
  ALKILN_TAG_EXPRESSION: "${{ env.ALKILN_TAG_EXPRESSION }}"
84
84
  #### Developer note: You can probably leave this out
85
- # ALKILN_VERSION:
85
+ ALKILN_VERSION: "@race"
package/CHANGELOG.md CHANGED
@@ -43,7 +43,21 @@ Format:
43
43
  -
44
44
  -->
45
45
 
46
- <!-- ## [Unreleased] -->
46
+ ## [Unreleased]
47
+
48
+ ### Fixed
49
+
50
+ - Fixed race condition between navigating and extracting button text.
51
+
52
+ ## [5.15.1] - 2025-08-05
53
+
54
+ ### Changed
55
+
56
+ - Updated dependencies. See [#1012](https://github.com/SuffolkLITLab/ALKiln/issues/1012).
57
+
58
+ ### Fixed
59
+
60
+ - Fix pdfjs version mismatch with docassemble's most recent version of node. See ALKilnInThePlayground https://github.com/SuffolkLITLab/docassemble-ALKilnInThePlayground/issues/42.
47
61
 
48
62
  ## [5.15.0] - 2025-05-22
49
63
 
package/lib/scope.js CHANGED
@@ -7,6 +7,7 @@ const { AxePuppeteer } = require('@axe-core/puppeteer');
7
7
  const safe_filename = require("sanitize-filename");
8
8
  // "en" is faster to import: https://github.com/faker-js/faker/issues/1114#issuecomment-1169532948
9
9
  const faker = require('@faker-js/faker/locale/en').faker;
10
+ const { v4: uuidv4 } = require('uuid');
10
11
 
11
12
  // Ours
12
13
  const pdf_reader = require('./utils/pdf_reader');
@@ -254,23 +255,21 @@ module.exports = {
254
255
  if ( options.waitForTimeout ) { await time.waitForTimeout( options.waitForTimeout ); }
255
256
  }, // Ends afterStep
256
257
 
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`. */
258
+ tapElementAndNavigate: async function(scope, { elem, waitForTimeout=null }) {
259
+ /** Tap a given element, detect navigation, and do appropriate related things. */
260
260
 
261
261
  let start_url = await scope.page.url(); // so we can later check for navigation
262
262
 
263
263
  await scope.tapElementAndStayOnPage(scope, {
264
264
  elem,
265
- click_promise: Promise.all([
266
- scope.page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
267
- elem.evaluate( (el) => { return el.click(); })
268
- ])
265
+ promise_returners: [
266
+ () => { return scope.nav_race(scope, {}).result; },
267
+ () => { return elem.evaluate( (el) => { return el.click(); }) },
268
+ ]
269
269
  });
270
270
 
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?
271
+ // For other ideas for detecting navigation, see
272
+ // https://github.com/SuffolkLITLab/ALKiln/issues/229
274
273
  let end_url = await scope.page.url();
275
274
  let navigated = start_url !== end_url;
276
275
 
@@ -282,36 +281,77 @@ module.exports = {
282
281
  let { question_has_id, id, id_matches } = await scope.examinePageID( scope, 'none to match' );
283
282
 
284
283
  // If navigation, wait for 200ms to let things finish loading and moving
285
- await scope.afterStep(scope, { waitForTimeout: 200, navigated: true });
284
+ await scope.afterStep(scope, {
285
+ waitForTimeout: ( waitForTimeout || 200 ),
286
+ navigated: true
287
+ });
286
288
  } else {
287
- await scope.afterStep(scope, { waitForTimeout, navigated: false });
289
+ // Discuss: Fail here or later since the test didn't manage to navigate?
290
+ await scope.afterStep(scope, {
291
+ waitForTimeout: ( waitForTimeout || 0 ),
292
+ navigated: false
293
+ });
288
294
  }
289
295
  }, // Ends scope.tapElementAndNavigate()
290
296
 
291
- tapElementAndStayOnPage: async function(scope, { elem, click_promise }) {
297
+ nav_race: function(scope, opts={}) {
298
+ /**
299
+ * Return a Promise for a race that detects which, if any, navigation result
300
+ * a navigation had.
301
+ *
302
+ * Warning: Unable to effectively add event listener for `daPageLoad`. Gets
303
+ * overwritten on every page load.
304
+ *
305
+ * @returns
306
+ * { obj }
307
+ * ok { bool } - True if no unexpected errors
308
+ * messages {[ str ]} - Any error or info messages
309
+ * errors {[ Error ]} - Any Errors that happened
310
+ * result { Promise } with result of { obj } - HTTP response or other data
311
+ * aborters {[ obj ]} - Abort signals for all the Promises involved
312
+ * */
313
+ let ok, messages, errors;
314
+ let aborters = [];
315
+
316
+ let result = Promise.race([
317
+ scope.page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
318
+ ]);
319
+
320
+ return { result, aborters, ok, messages, errors, };
321
+ }, // Ends scope.nav_race()
322
+
323
+ tapElementAndStayOnPage: async function(scope, { elem, promise_returners=null }) {
292
324
  /** Tap a given element, detect navigation, and do appropriate related things.
293
- * `click_promise can be any kind of Promise, including a Promise.all()
325
+ *
326
+ * @param elem { obj } - element to tap
327
+ * @param promise_returners {[ func -> Promise ]} - list of functions that
328
+ * return promises
294
329
  */
295
330
  await scope.guard_against_missing_tap_element(scope, { elem });
296
331
 
297
332
  // Get the text on the button or link for a potential error message later
298
333
  let tapperText = await (await elem.getProperty('textContent')).jsonValue();
299
334
 
300
- click_promise = click_promise || elem.click();
335
+ let click_promises = null;
336
+ if ( promise_returners ) {
337
+ click_promises = Promise.all(promise_returners.map( pr_ret => pr_ret() ));
338
+ } else {
339
+ click_promises = elem.click();
340
+ }
301
341
 
302
342
  // TODO: implement and use scope.handle_possible_timeout()
303
343
  try {
304
- // Serever error loads page, so keeping things shorter by not looking
344
+ // Server error loads page, so keeping things shorter by not looking
305
345
  // for that text too. Also, puppeteer will ensure proper timeout.
306
346
  await Promise.race([
307
- click_promise,
347
+ click_promises,
308
348
  // This is meant to detect user-visible alerts/errors,
309
349
  // not breaking errors in the testing code.
350
+ // No need for aborters since puppeteer has its own timeout
310
351
  scope.page.waitForSelector('.alert-danger', { visible: true }),
311
352
  scope.page.waitForSelector('.da-has-error', { visible: true }),
312
353
  ]);
313
354
  } catch ( error ) {
314
-
315
355
  if ( error.name === `TimeoutError` ) {
316
356
  let non_reload_report_msg = `After ALKiln tapped "${ tapperText }" it took too long to load the next page.`;
317
357
  await scope.handle_page_timeout_error( scope, { non_reload_report_data: {
@@ -1,10 +1,17 @@
1
1
  // All functions dealing with getting information from PDFs
2
2
 
3
- const pdfjs_lib = require("pdfjs-dist/legacy/build/pdf.js");
4
3
  const diff_lib = require("diff");
5
4
 
5
+ async function importPdfLib() {
6
+ // pdfjs only supports nodejs in its legacy build
7
+ // https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions/c47467dd05966b686af96f55ce061dab2f96c306
8
+ const pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
9
+ return pdfjsLib;
10
+ }
11
+
6
12
  let _load_pdf = async function(pdf_path) {
7
13
  // Simple wrapper to get the document
14
+ let pdfjs_lib = await importPdfLib();
8
15
  return await pdfjs_lib.getDocument({
9
16
  url: pdf_path,
10
17
  // https://github.com/mozilla/pdf.js/issues/4244#issuecomment-1479534301
@@ -110,4 +117,4 @@ pdf_reader.compare_pdfs = async function(existing_pdf_path, new_pdf_path) {
110
117
  return null;
111
118
  }; // End compare_pdfs
112
119
 
113
- module.exports = pdf_reader;
120
+ module.exports = pdf_reader;