From 5c263a0fc89debac8aa53004bb41f5f66de2be42 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:19:54 +0100 Subject: [PATCH 01/19] [O2B-1517] Fix table row loading check Number of rows must be different to that of the previous test otherwise waitForTable will execute and move on before the new data has been loaded. Therefore new test case added that will result in different number of rows prior. --- test/public/runs/runsPerLhcPeriod.overview.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/public/runs/runsPerLhcPeriod.overview.test.js b/test/public/runs/runsPerLhcPeriod.overview.test.js index 3a00301943..df55d6e450 100644 --- a/test/public/runs/runsPerLhcPeriod.overview.test.js +++ b/test/public/runs/runsPerLhcPeriod.overview.test.js @@ -153,10 +153,16 @@ module.exports = () => { const amountSelectorButtonSelector = `${amountSelectorId} button`; await pressElement(page, amountSelectorButtonSelector); + await fillInput(page, `${amountSelectorId} input[type=number]`, '3', ['input', 'change']); + await waitForTableLength(page, 3); + await expectInnerText(page, '.dropup button', 'Rows per page: 3 '); + + await pressElement(page, amountSelectorButtonSelector); await page.waitForSelector(`${amountSelectorId} .dropup-menu`); const amountItems5 = `${amountSelectorId} .dropup-menu .menu-item:first-child`; await pressElement(page, amountItems5, true); + // only 4 runs in LHC Period 1 await waitForTableLength(page, 4); await expectInnerText(page, '.dropup button', 'Rows per page: 5 '); From 33150afe67af50378c9b17a38e1c76597bd5c0f1 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:51:35 +0100 Subject: [PATCH 02/19] [O2B-1517] Fix order of assertions in runs per data pass test Moved the assertion for the dropup button text after waiting for the table length. --- test/public/runs/runsPerDataPass.overview.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index 090384e0f6..0735f5bbb6 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -242,8 +242,8 @@ module.exports = () => { await pressElement(page, amountItems5); // Expect the amount of visible runs to reduce when the first option (5) is selected - await expectInnerText(page, '.dropup button', 'Rows per page: 5 '); await waitForTableLength(page, 4); + await expectInnerText(page, '.dropup button', 'Rows per page: 5 '); // Expect the custom per page input to have red border and text color if wrong value typed const customPerPageInput = await page.$(`${amountSelectorId} input[type=number]`); From ced51116459cc8c1618548aeda72b18fea342f17 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:19:35 +0100 Subject: [PATCH 03/19] [O2b-1517] Add table length wait in runsPerLhcPeriod overview test Inserted calls to waitForTableLength before table data validation to ensure the table is fully loaded before assertions. --- test/public/runs/runsPerLhcPeriod.overview.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/public/runs/runsPerLhcPeriod.overview.test.js b/test/public/runs/runsPerLhcPeriod.overview.test.js index df55d6e450..113aceb06a 100644 --- a/test/public/runs/runsPerLhcPeriod.overview.test.js +++ b/test/public/runs/runsPerLhcPeriod.overview.test.js @@ -110,6 +110,7 @@ module.exports = () => { ...Object.fromEntries(DETECTORS.map((detectorName) => [detectorName, (quality) => expect(quality).oneOf([...RUN_QUALITIES, ''])])), }; + await waitForTableLength(page, 4); await validateTableData(page, new Map(Object.entries(tableDataValidatorsWithDetectorQualities))); await waitForNavigation(page, () => pressElement(page, '#synchronousFlags-tab')); @@ -122,6 +123,7 @@ module.exports = () => { ])), }; + await waitForTableLength(page, 4); await validateTableData(page, new Map(Object.entries(tableDataValidatorsWithQualityFromSynchronousFlags))); await expectInnerText(page, '#row56-FT0', '83'); }); From 1f0b5d0429495d89db262fc82377a5ea05d0d2f8 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:02:42 +0100 Subject: [PATCH 04/19] [O2B-1517] Add timeout option to waitForTableToLength helper The waitForTableToLength function now accepts an optional timeout parameter. Updated overview.test.js to use a 5000ms timeout when waiting for table rows in a troublesome test. --- test/public/defaults.js | 6 ++++-- test/public/envs/overview.test.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/public/defaults.js b/test/public/defaults.js index bb7ae4b811..c184c60d37 100644 --- a/test/public/defaults.js +++ b/test/public/defaults.js @@ -145,13 +145,15 @@ module.exports.waitForTimeout = waitForTimeout; * * @param {puppeteer.Page} page - The puppeteer page where the table is located. * @param {number} expectedSize - The expected number of table rows, excluding rows marked as loading or empty. + * @param {number} [timeout] - Max wait time in ms; if omitted, uses the page default timeout. * @return {Promise} Resolves once the expected number of rows is met, or the timeout is reached. */ -const waitForTableToLength = async (page, expectedSize) => { +const waitForTableToLength = async (page, expectedSize, timeout) => { try { + const waitOptions = timeout === undefined ? {} : { timeout }; await page.waitForFunction( (expectedSize) => document.querySelectorAll('table tbody tr:not(.loading-row):not(.empty-row)').length === expectedSize, - {}, + waitOptions, expectedSize, ); } catch { diff --git a/test/public/envs/overview.test.js b/test/public/envs/overview.test.js index 1b9fa872c6..e253b403ec 100644 --- a/test/public/envs/overview.test.js +++ b/test/public/envs/overview.test.js @@ -424,7 +424,7 @@ module.exports = () => { await fillInput(page, selector.fromDateSelector, fromDate, ['change']); await fillInput(page, selector.toDateSelector, toDate, ['change']); - await waitForTableLength(page, expectedIds.length); + await waitForTableLength(page, expectedIds.length, 5000); expect(await page.$$eval('tbody tr', (rows) => rows.map((row) => row.id))).to.eql(expectedIds.map(id => `row${id}`)); }; From bd03df7912d2d8f62a28443c134869b608380478 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:25:08 +0100 Subject: [PATCH 05/19] [O2B-1517] Fix selector for loading row in table test Corrects the selector for detecting loading rows. It should check for 'tbody' not body and understand that it can return an empty list and the double ! operator will treat this as a truthy value which is incorrect. --- test/public/defaults.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/public/defaults.js b/test/public/defaults.js index c184c60d37..443de2bacf 100644 --- a/test/public/defaults.js +++ b/test/public/defaults.js @@ -158,7 +158,7 @@ const waitForTableToLength = async (page, expectedSize, timeout) => { ); } catch { const actualSize = (await page.$$('tbody tr')).length; - const isThereLoadingRow = !!(await page.$$('table body tr.loading-row')) + const isThereLoadingRow = (await page.$$('table tbody tr.loading-row')).length > 0; throw new Error(`Expected table of length ${expectedSize}, but got ${actualSize} ${isThereLoadingRow ? ', loading-row' : ''}`); } }; From d147e661a33fb29600fd9fd831504dec82f038bd Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:56:14 +0100 Subject: [PATCH 06/19] [O2B-1517] Add table length wait after filter resets in tests Confirm reset is finished before proceeding with further test actions. Issue with reset and debounce and assertions. --- test/public/envs/overview.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/public/envs/overview.test.js b/test/public/envs/overview.test.js index e253b403ec..02b5511cf2 100644 --- a/test/public/envs/overview.test.js +++ b/test/public/envs/overview.test.js @@ -442,6 +442,7 @@ module.exports = () => { ['eZF99lH6'], ); await resetFilters(page); + await waitForTableLength(page, 8, 10000); await filterOnCreatedAt( periodInputsSelectors, @@ -452,5 +453,6 @@ module.exports = () => { ['GIDO1jdkD', '8E4aZTjY', 'Dxi029djX'], ); await resetFilters(page); + await waitForTableLength(page, 8, 10000); }); }; From c923f315991afb06a9ad513150087ffb2053e045 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 7 Jan 2026 14:31:51 +0100 Subject: [PATCH 07/19] [O2B-1517] Update expected table length in env overview tests Adjusted the expected table length from 8 to 9 in two assertions. --- test/public/envs/overview.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/public/envs/overview.test.js b/test/public/envs/overview.test.js index 02b5511cf2..8fa2c38213 100644 --- a/test/public/envs/overview.test.js +++ b/test/public/envs/overview.test.js @@ -442,7 +442,7 @@ module.exports = () => { ['eZF99lH6'], ); await resetFilters(page); - await waitForTableLength(page, 8, 10000); + await waitForTableLength(page, 9, 10000); await filterOnCreatedAt( periodInputsSelectors, @@ -453,6 +453,6 @@ module.exports = () => { ['GIDO1jdkD', '8E4aZTjY', 'Dxi029djX'], ); await resetFilters(page); - await waitForTableLength(page, 8, 10000); + await waitForTableLength(page, 9, 10000); }); }; From 63b1e8dd030c9902c5683b0b035fe17dde802f0d Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:46:00 +0100 Subject: [PATCH 08/19] Improve selector usage and reliability in overview tests Refactored test selectors to use more specific and reliable queries, added explicit timeouts for modal waits, and improved element handling for dropdowns and navigation. These changes enhance test stability and reduce flakiness in the runs overview test suite. --- test/public/runs/overview.test.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/test/public/runs/overview.test.js b/test/public/runs/overview.test.js index 14065478f7..3616a820f4 100644 --- a/test/public/runs/overview.test.js +++ b/test/public/runs/overview.test.js @@ -255,10 +255,10 @@ module.exports = () => { it('can navigate to a run detail page', async () => { await navigateToRunsOverview(page); - await page.waitForSelector('tbody tr'); - const expectedRunNumber = await page.evaluate(() => document.querySelector('tbody tr:first-of-type a').innerText); + const firstLink = await page.waitForSelector('tbody tr:first-of-type a'); + const expectedRunNumber = await firstLink.evaluate((el) => el.innerText); - await waitForNavigation(page, () => page.evaluate(() => document.querySelector('tbody tr:first-of-type a').click())); + await waitForNavigation(page, () => firstLink.evaluate((el) => el.click())); const redirectedUrl = await page.url(); const urlParameters = redirectedUrl.slice(redirectedUrl.indexOf('?') + 1).split('&'); @@ -775,12 +775,13 @@ module.exports = () => { it('should successfully filter by EOR Reason types', async () => { // Expect the EOR filter to exist await page.waitForSelector('#eorCategories'); - const eorTitleDropdown = await page.waitForSelector('#eorTitles'); + await page.waitForSelector('#eorTitles'); // Select the EOR reason category DETECTORS await page.select('#eorCategories', 'DETECTORS'); await waitForTableLength(page, 3); - let detectorTitleElements = await eorTitleDropdown.$$('option'); + await page.waitForSelector('#eorTitles option'); + let detectorTitleElements = await page.$$('#eorTitles option'); expect(detectorTitleElements).has.lengthOf(3); // The titles dropdown should have updated @@ -819,7 +820,7 @@ module.exports = () => { // Reset filters. There should be a single blank option in the EOR titles dropdown await resetFilters(page) await waitForTableLength(page, 10); - detectorTitleElements = await eorTitleDropdown.$$('option'); + detectorTitleElements = await page.$$('#eorTitles option'); expect(detectorTitleElements).has.lengthOf(1); // There should be many items in the run details table @@ -876,7 +877,7 @@ module.exports = () => { expect(exportModal).to.be.null; await page.$eval(EXPORT_RUNS_TRIGGER_SELECTOR, (button) => button.click()); - await page.waitForSelector('#export-data-modal'); + await page.waitForSelector('#export-data-modal', { timeout: 5000 }); exportModal = await page.$('#export-data-modal'); expect(exportModal).to.not.be.null; @@ -908,8 +909,8 @@ module.exports = () => { await pressElement(page, EXPORT_RUNS_TRIGGER_SELECTOR, true) await page.waitForSelector('#export-data-modal'); await page.waitForSelector('#send:disabled'); - await page.waitForSelector('.form-control'); - await page.select('.form-control', 'runQuality', 'runNumber'); + await page.waitForSelector('#export-data-modal select.form-control'); + await page.select('#export-data-modal select.form-control', 'runQuality', 'runNumber'); await page.waitForSelector('#send:enabled'); const exportButtonText = await page.$eval('#send', (button) => button.innerText); expect(exportButtonText).to.be.eql('Export'); @@ -945,10 +946,10 @@ module.exports = () => { ///// Download await page.$eval(EXPORT_RUNS_TRIGGER_SELECTOR, (button) => button.click()); - await page.waitForSelector('#export-data-modal'); + await page.waitForSelector('#export-data-modal', { timeout: 5000 }); - await page.waitForSelector('.form-control'); - await page.select('.form-control', 'runQuality', 'runNumber'); + await page.waitForSelector('#export-data-modal select.form-control'); + await page.select('#export-data-modal select.form-control', 'runQuality', 'runNumber'); { const downloadPath = await waitForDownload(page, () => pressElement(page, '#send:enabled', true)); From 397c6b6d0f4ee85914b290e1eee5815d84c639c6 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:30:54 +0100 Subject: [PATCH 09/19] Fix tests to make more reliable Add longer timeout to test and add loading check to resetFilters function. --- test/public/defaults.js | 5 +++++ test/public/runs/overview.test.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/test/public/defaults.js b/test/public/defaults.js index 443de2bacf..952059441d 100644 --- a/test/public/defaults.js +++ b/test/public/defaults.js @@ -943,4 +943,9 @@ module.exports.resetFilters = async (page) => { await this.pressElement(page, '#reset-filters', true); await this.pressElement(page, '#openFilterToggle', true); }); + + await page.waitForFunction( + () => document.querySelectorAll('table tbody tr.loading-row').length === 0, + { timeout: 5000 }, + ); }; diff --git a/test/public/runs/overview.test.js b/test/public/runs/overview.test.js index 3616a820f4..e0f54d9054 100644 --- a/test/public/runs/overview.test.js +++ b/test/public/runs/overview.test.js @@ -940,7 +940,7 @@ module.exports = () => { await openFilteringPanel(page);; await page.waitForSelector(badFilterSelector); await page.$eval(badFilterSelector, (element) => element.click()); - await page.waitForSelector('.atom-spinner'); + await page.waitForSelector('.atom-spinner', { timeout: 5000 }); await page.waitForSelector('tbody tr:nth-child(2)'); await page.waitForSelector(EXPORT_RUNS_TRIGGER_SELECTOR); From 334963d88f9334f98da56320a53e672fab477840 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:54:58 +0100 Subject: [PATCH 10/19] Fix test with a timeout Replaced waitForTableLength with expectInnerText and 5000ms timeout. This improves test reliability by waiting for the expected text to appear within a specified time, instead of using waitForTableLength which sometimes passes too quick because previous length is also 3. --- test/public/runs/runsPerDataPass.overview.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index 0735f5bbb6..6d14129506 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -197,8 +197,7 @@ module.exports = () => { innerText: '89', }); await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); - await waitForTableLength(page, 3); - await expectInnerText(page, 'tr#row106 .column-CPV a', '67MC.R'); + await expectInnerText(page, 'tr#row106 .column-CPV a', '67MC.R', { timeout: 5000 }); await expectLink(page, 'tr#row106 .column-CPV a', { href: 'http://localhost:4000/?page=qc-flags-for-data-pass&runNumber=106&dplDetectorId=1&dataPassId=1', innerText: '67MC.R', From d19e3c912fc18372c03eebc13d32cd73fde8b102 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Mon, 12 Jan 2026 16:08:52 +0100 Subject: [PATCH 11/19] Add polling option to expectInnerText in test Updated the test to include a polling option with 'mutation' for expectInnerText, improving reliability when waiting for text changes in the DOM. --- test/public/runs/runsPerDataPass.overview.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index 6d14129506..5b37ede17e 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -198,6 +198,7 @@ module.exports = () => { }); await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); await expectInnerText(page, 'tr#row106 .column-CPV a', '67MC.R', { timeout: 5000 }); + await expectInnerText(page, 'tr#row106 .column-CPV a', '67MC.R', { timeout: 5000, polling: 'mutation' }); await expectLink(page, 'tr#row106 .column-CPV a', { href: 'http://localhost:4000/?page=qc-flags-for-data-pass&runNumber=106&dplDetectorId=1&dataPassId=1', innerText: '67MC.R', From 13f14583b99d11fdb22f79a8a0437b382c2ae037 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Mon, 12 Jan 2026 16:09:30 +0100 Subject: [PATCH 12/19] Removed a redundant expectInnerText call... Removed a redundant expectInnerText call... --- test/public/runs/runsPerDataPass.overview.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index 5b37ede17e..bb6d30e4ac 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -197,7 +197,6 @@ module.exports = () => { innerText: '89', }); await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); - await expectInnerText(page, 'tr#row106 .column-CPV a', '67MC.R', { timeout: 5000 }); await expectInnerText(page, 'tr#row106 .column-CPV a', '67MC.R', { timeout: 5000, polling: 'mutation' }); await expectLink(page, 'tr#row106 .column-CPV a', { href: 'http://localhost:4000/?page=qc-flags-for-data-pass&runNumber=106&dplDetectorId=1&dataPassId=1', From 4395aeeded9b3f9e75d57787de4f8da6233243ae Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 13 Jan 2026 09:32:16 +0100 Subject: [PATCH 13/19] [O2B-1517] Improve waitForTableToLength to avoid false positives Added an optional oldTable parameter to waitForTableToLength to compare previous and current table HTML, preventing false positives when waiting for table row changes. Also fixed row counting to exclude loading and empty rows in error handling. --- test/public/defaults.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/test/public/defaults.js b/test/public/defaults.js index 952059441d..949ef17e56 100644 --- a/test/public/defaults.js +++ b/test/public/defaults.js @@ -146,20 +146,33 @@ module.exports.waitForTimeout = waitForTimeout; * @param {puppeteer.Page} page - The puppeteer page where the table is located. * @param {number} expectedSize - The expected number of table rows, excluding rows marked as loading or empty. * @param {number} [timeout] - Max wait time in ms; if omitted, uses the page default timeout. - * @return {Promise} Resolves once the expected number of rows is met, or the timeout is reached. + * @param {string} [oldTable] - Optional HTML content of the previous table state to avoid false positives. +* @return {Promise} Resolves once the expected number of rows is met, or the timeout is reached. */ -const waitForTableToLength = async (page, expectedSize, timeout) => { +const waitForTableToLength = async (page, expectedSize, timeout, oldTable) => { try { const waitOptions = timeout === undefined ? {} : { timeout }; await page.waitForFunction( - (expectedSize) => document.querySelectorAll('table tbody tr:not(.loading-row):not(.empty-row)').length === expectedSize, + (expectedSize, oldTable) => { + const actualSize = document.querySelectorAll('table tbody tr:not(.loading-row):not(.empty-row)').length; + if (actualSize !== expectedSize) { + return false; + } + // compare old table contents to avoid false positives + if (oldTable) { + const currentTable = document.querySelector('table').innerHTML; + return currentTable !== oldTable; + } + return true; + }, waitOptions, expectedSize, + oldTable, ); } catch { - const actualSize = (await page.$$('tbody tr')).length; + const actualSize = (await page.$$('tbody tr:not(.loading-row):not(.empty-row)')).length; const isThereLoadingRow = (await page.$$('table tbody tr.loading-row')).length > 0; - throw new Error(`Expected table of length ${expectedSize}, but got ${actualSize} ${isThereLoadingRow ? ', loading-row' : ''}`); + throw new Error(`Expected table of length ${expectedSize}, but got ${actualSize}${isThereLoadingRow ? ' (loading-row present)' : ''}`); } }; From d4d226e8ef1e45ce174f522b4142d59090ec379b Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 13 Jan 2026 09:34:26 +0100 Subject: [PATCH 14/19] [O2B-1517] Improve table update checks Pass previous table HTML to waitForTableLength in several tests to ensure table updates are detected more reliably. --- test/public/runs/runsPerDataPass.overview.test.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index bb6d30e4ac..f541bb2cf1 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -190,14 +190,17 @@ module.exports = () => { it('should switch mcReproducibleAsNotBad', async () => { await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); - await waitForTableLength(page, 3); + let oldTable = await page.waitForSelector('table').then((table) => table.evaluate((t) => t.innerHTML)); + await waitForTableLength(page, 3, undefined, oldTable); await expectInnerText(page, 'tr#row106 .column-CPV a', '89'); await expectLink(page, 'tr#row106 .column-CPV a', { href: 'http://localhost:4000/?page=qc-flags-for-data-pass&runNumber=106&dplDetectorId=1&dataPassId=1', innerText: '89', }); + oldTable = await page.waitForSelector('table').then((table) => table.evaluate((t) => t.innerHTML)); await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); - await expectInnerText(page, 'tr#row106 .column-CPV a', '67MC.R', { timeout: 5000, polling: 'mutation' }); + await waitForTableLength(page, 3, undefined, oldTable); + await expectInnerText(page, 'tr#row106 .column-CPV a', '67MC.R'); await expectLink(page, 'tr#row106 .column-CPV a', { href: 'http://localhost:4000/?page=qc-flags-for-data-pass&runNumber=106&dplDetectorId=1&dataPassId=1', innerText: '67MC.R', @@ -241,7 +244,8 @@ module.exports = () => { await pressElement(page, amountItems5); // Expect the amount of visible runs to reduce when the first option (5) is selected - await waitForTableLength(page, 4); + const oldTable = await page.waitForSelector('table').then((table) => table.evaluate((t) => t.innerHTML)); + await waitForTableLength(page, 4, 5000, oldTable); await expectInnerText(page, '.dropup button', 'Rows per page: 5 '); // Expect the custom per page input to have red border and text color if wrong value typed @@ -611,8 +615,9 @@ module.exports = () => { await pressElement(page, '#actions-dropdown-button .popover-trigger', true); setConfirmationDialogToBeAccepted(page); await pressElement(page, `${popoverSelector} button:nth-child(4)`, true); + const oldTable = await page.waitForSelector('table').then((table) => table.evaluate((t) => t.innerHTML)); await pressElement(page, '#actions-dropdown-button .popover-trigger', true); - await waitForTableLength(page, 3); + await waitForTableLength(page, 3, undefined, oldTable); // Processing of data might take a bit of time, but then expect QC flag button to be there await expectInnerText( page, From 4c25d3c816056ca05b9c074ba6afebfa1fee5aee Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 13 Jan 2026 09:47:20 +0100 Subject: [PATCH 15/19] [O2B-1517] Set longer timeout in mcRepod tests --- test/public/runs/runsPerDataPass.overview.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index f541bb2cf1..0ac6dc5918 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -191,7 +191,7 @@ module.exports = () => { it('should switch mcReproducibleAsNotBad', async () => { await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); let oldTable = await page.waitForSelector('table').then((table) => table.evaluate((t) => t.innerHTML)); - await waitForTableLength(page, 3, undefined, oldTable); + await waitForTableLength(page, 3, 5000, oldTable); await expectInnerText(page, 'tr#row106 .column-CPV a', '89'); await expectLink(page, 'tr#row106 .column-CPV a', { href: 'http://localhost:4000/?page=qc-flags-for-data-pass&runNumber=106&dplDetectorId=1&dataPassId=1', @@ -199,7 +199,7 @@ module.exports = () => { }); oldTable = await page.waitForSelector('table').then((table) => table.evaluate((t) => t.innerHTML)); await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); - await waitForTableLength(page, 3, undefined, oldTable); + await waitForTableLength(page, 3, 5000, oldTable); await expectInnerText(page, 'tr#row106 .column-CPV a', '67MC.R'); await expectLink(page, 'tr#row106 .column-CPV a', { href: 'http://localhost:4000/?page=qc-flags-for-data-pass&runNumber=106&dplDetectorId=1&dataPassId=1', From b1ee50329822382eeb8376ecd79d431ca9f5a40a Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 13 Jan 2026 09:53:58 +0100 Subject: [PATCH 16/19] [O2B-1517] Improve error message in waitForTableToLength The error message now includes information about whether the table content is unchanged, aiding in debugging test failures related to table updates. --- test/public/defaults.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/public/defaults.js b/test/public/defaults.js index 949ef17e56..7dc1d5ccd6 100644 --- a/test/public/defaults.js +++ b/test/public/defaults.js @@ -172,7 +172,9 @@ const waitForTableToLength = async (page, expectedSize, timeout, oldTable) => { } catch { const actualSize = (await page.$$('tbody tr:not(.loading-row):not(.empty-row)')).length; const isThereLoadingRow = (await page.$$('table tbody tr.loading-row')).length > 0; - throw new Error(`Expected table of length ${expectedSize}, but got ${actualSize}${isThereLoadingRow ? ' (loading-row present)' : ''}`); + const currentTable = await page.$eval('table', (t) => t.innerHTML).catch(() => null); + const tableUnchanged = oldTable && currentTable === oldTable; + throw new Error(`Expected table of length ${expectedSize}, but got ${actualSize}${isThereLoadingRow ? ' (loading-row present)' : ''}${tableUnchanged ? ' (table content unchanged)' : ''}`); } }; From 439522b00ff0d01dc675dcf10656fdb069e1c0b9 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:04:21 +0100 Subject: [PATCH 17/19] [O2B-1517] Remove incorrect waitFortTableLength usage Test was expanded to make use of extra waitForTableLength features but in hindsight this test should not use them. --- test/public/runs/runsPerDataPass.overview.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index 0ac6dc5918..5aac22054f 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -244,8 +244,7 @@ module.exports = () => { await pressElement(page, amountItems5); // Expect the amount of visible runs to reduce when the first option (5) is selected - const oldTable = await page.waitForSelector('table').then((table) => table.evaluate((t) => t.innerHTML)); - await waitForTableLength(page, 4, 5000, oldTable); + await waitForTableLength(page, 4); await expectInnerText(page, '.dropup button', 'Rows per page: 5 '); // Expect the custom per page input to have red border and text color if wrong value typed From d2eff6ed8ff818b867e8bfb8a5e62c30e20469e8 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:13:06 +0100 Subject: [PATCH 18/19] [O2B-1517] Fix order of actions in mcReproducibleAsNotBad test Moved the call to pressElement after capturing the old table HTML to ensure the correct state is compared. --- test/public/runs/runsPerDataPass.overview.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index 5aac22054f..d52f0b447a 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -189,8 +189,8 @@ module.exports = () => { }); it('should switch mcReproducibleAsNotBad', async () => { - await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); let oldTable = await page.waitForSelector('table').then((table) => table.evaluate((t) => t.innerHTML)); + await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); await waitForTableLength(page, 3, 5000, oldTable); await expectInnerText(page, 'tr#row106 .column-CPV a', '89'); await expectLink(page, 'tr#row106 .column-CPV a', { From 3c125dfd6b1f0b2f5209404678590371ff83a675 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:31:23 +0100 Subject: [PATCH 19/19] [O2B-1517] Improve local runs test reliability Added and improved waitForSelector statements in multiple test files. --- test/public/runs/detail.test.js | 2 ++ test/public/runs/overview.test.js | 8 ++++---- test/public/runs/runsPerDataPass.overview.test.js | 3 +++ test/public/runs/runsPerLhcPeriod.overview.test.js | 2 ++ test/public/runs/runsPerSimulationPass.overview.test.js | 1 + 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/public/runs/detail.test.js b/test/public/runs/detail.test.js index 9d03403f40..fa94143746 100644 --- a/test/public/runs/detail.test.js +++ b/test/public/runs/detail.test.js @@ -521,8 +521,10 @@ module.exports = () => { it('should successfully display links to infologger and QCG', async () => { await waitForNavigation(page, () => pressElement(page, 'a#run-overview')); + await page.waitForSelector('#row108 a'); await waitForNavigation(page, () => pressElement(page, '#row108 a')); + await page.waitForSelector('a.external-link'); await expectLink(page, 'a.external-link', { innerText: 'FLP', href: 'http://localhost:8081/?q={%22run%22:{%22match%22:%22108%22},%22severity%22:{%22in%22:%22W%20E%20F%22}}', diff --git a/test/public/runs/overview.test.js b/test/public/runs/overview.test.js index e0f54d9054..5ceac005b2 100644 --- a/test/public/runs/overview.test.js +++ b/test/public/runs/overview.test.js @@ -778,8 +778,9 @@ module.exports = () => { await page.waitForSelector('#eorTitles'); // Select the EOR reason category DETECTORS + const oldTable = await page.waitForSelector('table').then((table) => table.evaluate((t) => t.innerHTML)); await page.select('#eorCategories', 'DETECTORS'); - await waitForTableLength(page, 3); + await waitForTableLength(page, 3, undefined, oldTable); await page.waitForSelector('#eorTitles option'); let detectorTitleElements = await page.$$('#eorTitles option'); expect(detectorTitleElements).has.lengthOf(3); @@ -907,7 +908,7 @@ module.exports = () => { // First export await pressElement(page, EXPORT_RUNS_TRIGGER_SELECTOR, true) - await page.waitForSelector('#export-data-modal'); + await page.waitForSelector('#export-data-modal', { timeout: 5000 }); await page.waitForSelector('#send:disabled'); await page.waitForSelector('#export-data-modal select.form-control'); await page.select('#export-data-modal select.form-control', 'runQuality', 'runNumber'); @@ -940,7 +941,6 @@ module.exports = () => { await openFilteringPanel(page);; await page.waitForSelector(badFilterSelector); await page.$eval(badFilterSelector, (element) => element.click()); - await page.waitForSelector('.atom-spinner', { timeout: 5000 }); await page.waitForSelector('tbody tr:nth-child(2)'); await page.waitForSelector(EXPORT_RUNS_TRIGGER_SELECTOR); @@ -948,7 +948,7 @@ module.exports = () => { await page.$eval(EXPORT_RUNS_TRIGGER_SELECTOR, (button) => button.click()); await page.waitForSelector('#export-data-modal', { timeout: 5000 }); - await page.waitForSelector('#export-data-modal select.form-control'); + await page.waitForSelector('#export-data-modal select.form-control', { timeout: 10000 }); await page.select('#export-data-modal select.form-control', 'runQuality', 'runNumber'); { diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index d52f0b447a..e8ea387b87 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -245,6 +245,7 @@ module.exports = () => { // Expect the amount of visible runs to reduce when the first option (5) is selected await waitForTableLength(page, 4); + await page.waitForSelector('.dropup button'); await expectInnerText(page, '.dropup button', 'Rows per page: 5 '); // Expect the custom per page input to have red border and text color if wrong value typed @@ -404,6 +405,7 @@ module.exports = () => { await expectColumnValues(page, 'runNumber', ['106']); + await page.waitForSelector('#openFilterToggle'); await pressElement(page, '#openFilterToggle'); await pressElement(page, '#reset-filters'); await expectColumnValues(page, 'runNumber', ['108', '107', '106']); @@ -422,6 +424,7 @@ module.exports = () => { */ await page.select('.runDuration-filter select', '>='); await fillInput(page, '#duration-operand', '10', ['change']); + await waitForTableLength(page, 2); await expectColumnValues(page, 'runNumber', ['55', '1']); diff --git a/test/public/runs/runsPerLhcPeriod.overview.test.js b/test/public/runs/runsPerLhcPeriod.overview.test.js index 113aceb06a..f38dc635a9 100644 --- a/test/public/runs/runsPerLhcPeriod.overview.test.js +++ b/test/public/runs/runsPerLhcPeriod.overview.test.js @@ -115,6 +115,8 @@ module.exports = () => { await waitForNavigation(page, () => pressElement(page, '#synchronousFlags-tab')); + await page.waitForSelector('tbody tr:not(.loading-row)'); + const tableDataValidatorsWithQualityFromSynchronousFlags = { ...tableDataValidators, ...Object.fromEntries(DETECTORS.map((detectorName) => [ diff --git a/test/public/runs/runsPerSimulationPass.overview.test.js b/test/public/runs/runsPerSimulationPass.overview.test.js index 89f6d4703c..b7b1c725fd 100644 --- a/test/public/runs/runsPerSimulationPass.overview.test.js +++ b/test/public/runs/runsPerSimulationPass.overview.test.js @@ -166,6 +166,7 @@ module.exports = () => { const amountItems5 = `${amountSelectorId} .dropup-menu .menu-item:first-child`; await pressElement(page, amountItems5); + await page.waitForSelector(`${amountSelectorId} .dropup-menu`); await fillInput(page, `${amountSelectorId} input[type=number]`, 1111); await page.waitForSelector(amountSelectorId); });