From dd834dabf29e3448605cb142c463cb77fe3a03bb Mon Sep 17 00:00:00 2001 From: Steve Churchill Date: Fri, 27 Mar 2026 10:25:14 +0000 Subject: [PATCH] Add large-data timeout and API wait helpers Introduce LARGE_DATA_TIMEOUT and replace fragile canvas waits with network-based waits to better handle slow/large data loads. Key changes: - e2etests/playwright.config.ts: add LARGE_DATA_TIMEOUT (30000ms) for long-running operations. - e2etests/pages/base-page.ts: use LARGE_DATA_TIMEOUT as default for several waits, add noDataMessage locator, implement waitForAPIResponseByPartialTextMatch and waitForFirstAPIResponseByPartialTextMatch helpers, refine waitForAllCanvases signature, and harden selectFilter option matching (escape + regex). - e2etests/pages/resources-page.ts: import LARGE_DATA_TIMEOUT and switch tab/navigation/chart waits to rely on the new API wait helpers (and progress-bar waits) instead of canvas checks; make several methods accept optional wait flags or timeouts; minor locator robustness improvements. - e2etests/tests/*.spec.ts: update tests to use the new navigateToResourcesPageAndResetFilters and to call selectCategorizeBy without implicit waits where appropriate, adding explicit API/canvas waits where needed. These updates improve reliability on slow CI or when pages load large datasets by waiting for specific API responses rather than relying solely on canvas rendering heuristics. --- e2etests/pages/base-page.ts | 112 ++++++++++++++------ e2etests/pages/resources-page.ts | 125 +++++++++++++++-------- e2etests/playwright.config.ts | 7 ++ e2etests/tests/perspective-tests.spec.ts | 5 +- e2etests/tests/resources-tests.spec.ts | 22 ++-- 5 files changed, 185 insertions(+), 86 deletions(-) diff --git a/e2etests/pages/base-page.ts b/e2etests/pages/base-page.ts index 14020f888..3ed8b5d5e 100644 --- a/e2etests/pages/base-page.ts +++ b/e2etests/pages/base-page.ts @@ -2,6 +2,7 @@ import { Locator, Page } from '@playwright/test'; import fs from 'fs'; import path from 'path'; import { debugLog, errorLog } from '../utils/debug-logging'; +import { LARGE_DATA_TIMEOUT } from '../playwright.config'; /** * Abstract class representing the base structure for all pages. @@ -20,6 +21,7 @@ export abstract class BasePage { readonly warningColor: string; // Default color for warning state readonly errorColor: string; // Default color for error state readonly successColor: string; // Default color for success state + readonly noDataMessage: Locator; // Filters readonly filtersBox: Locator; @@ -65,6 +67,7 @@ export abstract class BasePage { this.warningColor = 'rgb(232, 125, 30)'; // Default color for warning state this.errorColor = 'rgb(187, 20, 37)'; // Default color for error state this.successColor = 'rgb(0, 120, 77)'; // Default color for success state + this.noDataMessage = this.main.getByText('No data to display'); //Filters this.filtersBox = this.main.locator('xpath=(//div[.="Filters:"])[1]/..'); @@ -196,7 +199,6 @@ export abstract class BasePage { return root.locator(`[data-test-id="${testId}"], [data-testid="${testId}"]`); } - /** * Selects an option from a combo box if it is not already selected. * @@ -290,7 +292,7 @@ export abstract class BasePage { * - If no `` elements are present on the page, this method will wait until * the timeout is reached. */ - async waitForCanvas(timeout: number = 20000): Promise { + async waitForCanvas(timeout: number = LARGE_DATA_TIMEOUT): Promise { await this.page.waitForFunction( () => { const canvases = document.querySelectorAll('canvas'); @@ -328,13 +330,60 @@ export abstract class BasePage { * since `every` returns `true` for an empty array. * - No explicit timeout parameter is exposed; the default Playwright function timeout applies. */ - async waitForAllCanvases(): Promise { - await this.page.waitForFunction(() => { - return Array.from(document.querySelectorAll('canvas')).every(canvas => { - const ctx = canvas.getContext('2d', { willReadFrequently: true }); - return ctx && ctx.getImageData(0, 0, canvas.width, canvas.height).data.some(pixel => pixel !== 0); - }); + async waitForAllCanvases(timeout: number = LARGE_DATA_TIMEOUT): Promise { + await this.page.waitForFunction( + () => { + return Array.from(document.querySelectorAll('canvas')).every(canvas => { + const ctx = canvas.getContext('2d', { willReadFrequently: true }); + return ctx && ctx.getImageData(0, 0, canvas.width, canvas.height).data.some(pixel => pixel !== 0); + }); + }, + null, + { timeout } + ); + } + + /** + * Waits for an API response whose URL contains the specified text. + * + * Listens for incoming network responses and resolves as soon as one is received + * whose URL includes `urlText` and has an HTTP 200 status code. + * + * @param {string} urlText - A substring to match against the URL of incoming responses. + * @param {number} timeout - Maximum time in milliseconds to wait for a matching response. + * Rejects if no matching response is received within this duration. + * @returns {Promise} Resolves when a matching 200 response is received. + * + * @example + * // Wait for the resources API to respond + * await basePage.waitForAPIResponseByPartialTextMatch('op=CleanExpenses', 30000); + * + * @remarks + * For waiting on any one of multiple possible URLs, use + * `waitForFirstAPIResponseByPartialTextMatch` instead. + */ + async waitForAPIResponseByPartialTextMatch(urlText: string, timeout: number): Promise { + debugLog(`Waiting for ${urlText} API response`); + await this.page.waitForResponse(response => response.url().includes(urlText) && response.status() === 200, { timeout }); + debugLog(`API response including ${urlText} received`); + } + + /** + * Waits for the first API response whose URL matches any of the provided strings. + * + * Resolves as soon as one matching response is received, ignoring the rest. + * Useful when multiple endpoints may satisfy a condition and only the first is needed. + * + * @param {string[]} urlTexts - Array of URL substrings to match against incoming responses. + * @param {number} timeout - Maximum time in milliseconds to wait for a matching response. + * @returns {Promise} Resolves when the first matching response is received. + */ + async waitForFirstAPIResponseByPartialTextMatch(urlTexts: string[], timeout: number): Promise { + debugLog(`Waiting for first API response matching any of: [${urlTexts.join(', ')}]`); + await this.page.waitForResponse(response => urlTexts.some(urlText => response.url().includes(urlText)) && response.status() === 200, { + timeout, }); + debugLog(`First matching API response received`); } /** @@ -690,7 +739,7 @@ export abstract class BasePage { * @param {number} [timeout=10000] - The maximum time to wait for the loading image to disappear, in milliseconds. * @returns {Promise} A promise that resolves when the loading image is no longer visible or exits early if the image is not present. */ - async waitForLoadingPageImgToDisappear(timeout: number = 20000): Promise { + async waitForLoadingPageImgToDisappear(timeout: number = LARGE_DATA_TIMEOUT): Promise { try { await this.loadingPageImg.first().waitFor({ timeout: 1000 }); } catch (_error) { @@ -714,7 +763,7 @@ export abstract class BasePage { * @param {number} [timeout=10000] - The maximum time to wait for the initialisation message to disappear, in milliseconds. * @returns {Promise} A promise that resolves when the initialisation message is no longer visible or exits early if the message is not present. */ - async waitForInitialisationToComplete(timeout: number = 20000): Promise { + async waitForInitialisationToComplete(timeout: number = LARGE_DATA_TIMEOUT): Promise { try { await this.initialisationMessage.first().waitFor({ timeout: 1000 }); } catch (_error) { @@ -979,27 +1028,6 @@ export abstract class BasePage { } } - /** - * Selects a filter and applies the specified filter option. - * - * @param {Locator} filter - The filter locator to select. - * @param {string} filterOption - The specific filter option to apply. - * @throws {Error} Throws an error if `filterOption` is not provided when `filter` is specified. - * @returns {Promise} A promise that resolves when the filter is applied. - */ - protected async selectFilter(filter: Locator, filterOption: string): Promise { - if (filter) { - if (!filterOption) { - throw new Error('filterOption must be provided when filter is specified'); - } - if (!(await filter.isVisible())) await this.showMoreFiltersBtn.click(); - await filter.click(); - - await this.filterPopover.getByLabel(filterOption).first().click(); - await this.filterApplyButton.click(); - } - } - /** * Retrieves the currently active filter button from the filters box. * @@ -1028,4 +1056,26 @@ export abstract class BasePage { getActiveFilter(): Locator { return this.filtersBox.locator('//button[contains(@class, "MuiButton-contained")]'); } + + /** + * Selects a filter and applies the specified filter option. + * + * @param {Locator} filter - The filter locator to select. + * @param {string} filterOption - The specific filter option to apply. + * @throws {Error} Throws an error if `filterOption` is not provided when `filter` is specified. + * @returns {Promise} A promise that resolves when the filter is applied. + */ + protected async selectFilter(filter: Locator, filterOption: string): Promise { + if (filter) { + if (!filterOption) { + throw new Error('filterOption must be provided when filter is specified'); + } + if (!(await filter.isVisible())) await this.showMoreFiltersBtn.click(); + await filter.click(); + + const escapedOption = filterOption.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + await this.filterPopover.getByLabel(new RegExp(`${escapedOption}$`)).click(); + await this.filterApplyButton.click(); + } + } } diff --git a/e2etests/pages/resources-page.ts b/e2etests/pages/resources-page.ts index dcf61255b..21d4a44d8 100644 --- a/e2etests/pages/resources-page.ts +++ b/e2etests/pages/resources-page.ts @@ -1,6 +1,7 @@ -import {BasePage} from './base-page'; -import {Locator, Page} from '@playwright/test'; -import {debugLog} from '../utils/debug-logging'; +import { BasePage } from './base-page'; +import { Locator, Page } from '@playwright/test'; +import { debugLog } from '../utils/debug-logging'; +import { LARGE_DATA_TIMEOUT } from '../playwright.config'; /** * Represents the Resources Page. @@ -211,63 +212,88 @@ export class ResourcesPage extends BasePage { /** * Navigates to the Resources page and resets all active filters. * - * This method navigates to `/resources`, waits for all progress bars to disappear, - * waits for the canvas to finish rendering, resets any active filters, waits for the - * page to fully load, and finally waits for the first resource item in the table to - * be present. This ensures the page is in a clean, fully loaded state before any - * test interactions begin. + * Performs the following steps: + * 1. Navigates to the `/resources` URL. + * 2. Waits for the first API response matching any of the expected data identifiers. + * 3. Waits for all progress bars to disappear. + * 4. Resets all active filters on the page. + * 5. Waits for the page to finish loading. * - * @returns {Promise} Resolves when the page is loaded, filters are reset, and - * the first resource table item is visible. + * @param {number} [timeout=LARGE_DATA_TIMEOUT] - Maximum time in milliseconds to wait for the + * initial API response. Defaults to `LARGE_DATA_TIMEOUT` to accommodate slow data loads. + * @returns {Promise} Resolves when the page is loaded and all filters have been reset. * * @example - * // Use in a beforeEach to ensure a clean state before each test - * test.beforeEach(async ({ resourcesPage }) => { - * await resourcesPage.navigateToResourcesPageAndResetFilters(); - * }); + * // Navigate and reset filters using the default timeout + * await resourcesPage.navigateToResourcesPageAndResetFilters(); * - * @remarks - * - Prefer this method over a bare `navigateToURL()` call when tests require a - * filter-free state and a populated resource table before proceeding. - * - The `firstResourceItemInTable` wait uses a 15 second timeout to account for - * slow data loading on the Resources page. + * @example + * // Navigate and reset filters with a custom timeout + * await resourcesPage.navigateToResourcesPageAndResetFilters(60000); */ - async navigateToResourcesPageAndResetFilters(): Promise { + async navigateToResourcesPageAndResetFilters(timeout: number = LARGE_DATA_TIMEOUT): Promise { await this.navigateToURL('/resources'); + await this.waitForFirstAPIResponseByPartialTextMatch( + ['op=CleanExpenses', 'resources_count', 'breakdown_tags', 'op=MetaBreakdown'], + timeout + ); await this.waitForAllProgressBarsToDisappear(); - await this.waitForCanvas(); await this.resetFilters(); await this.waitForPageLoad(); } /** * Clicks the "Expenses" tab on the Resources page. - * This method interacts with the `tabExpensesBtn` locator and waits for the canvas to update. * - * @returns {Promise} Resolves when the tab is clicked and the canvas is updated. + * If the tab is not already selected, it clicks the tab button and optionally + * waits for the breakdown expenses API response and all progress bars to disappear. + * + * @param {boolean} [wait=true] - Whether to wait for the API response and progress bars after clicking. + * Set to `false` to skip waiting, e.g. when chaining multiple interactions. + * @returns {Promise} Resolves when the tab is active and the optional wait is complete. + * + * @example + * // Click the Expenses tab and wait for data to load + * await resourcesPage.clickExpensesTab(); + * + * @example + * // Click without waiting + * await resourcesPage.clickExpensesTab(false); */ - async clickExpensesTab(wait = true): Promise { + async clickExpensesTab(wait: boolean = true): Promise { debugLog('Clicking ExpensesTab'); if ((await this.tabExpensesBtn.getAttribute('aria-selected')) !== 'true') { await this.tabExpensesBtn.click(); - } - if (wait) { - await this.waitForCanvas(); + if (wait) { + await this.waitForAPIResponseByPartialTextMatch('breakdown_expenses', LARGE_DATA_TIMEOUT); + } await this.waitForAllProgressBarsToDisappear(); } } /** * Clicks the "Resource Count" tab on the Resources page. - * This method interacts with the `tabResourceCountBtn` locator and waits for the canvas to update. * - * @returns {Promise} Resolves when the tab is clicked and the canvas is updated. + * Clicks the tab button and optionally waits for the resource count API + * response and all progress bars to disappear. + * + * @param {boolean} [wait=true] - Whether to wait for the API response and progress bars after clicking. + * Set to `false` to skip waiting, e.g. when chaining multiple interactions. + * @returns {Promise} Resolves when the tab is clicked and the optional wait is complete. + * + * @example + * // Click the Resource Count tab and wait for data to load + * await resourcesPage.clickResourceCountTab(); + * + * @example + * // Click without waiting + * await resourcesPage.clickResourceCountTab(false); */ async clickResourceCountTab(wait = true): Promise { debugLog('Clicking Resource Count tab'); await this.tabResourceCountBtn.click(); if (wait) { - await this.waitForCanvas(); + await this.waitForAPIResponseByPartialTextMatch('resources_count', LARGE_DATA_TIMEOUT); await this.waitForAllProgressBarsToDisappear(); } } @@ -282,7 +308,7 @@ export class ResourcesPage extends BasePage { debugLog('Clicking Tags Tab'); await this.tabTagsBtn.click(); if (wait) { - await this.waitForCanvas(); + await this.waitForAPIResponseByPartialTextMatch('breakdown_tags', LARGE_DATA_TIMEOUT); await this.waitForAllProgressBarsToDisappear(); } } @@ -307,11 +333,11 @@ export class ResourcesPage extends BasePage { * await resourcesPage.clickMetaTab(false); * await resourcesPage.selectMetaCategorizeBy('Region'); */ - async clickMetaTab(wait = true): Promise { + async clickMetaTab(wait: boolean = true): Promise { debugLog('Clicking Meta Tab'); await this.tabMetaBtn.click(); if (wait) { - await this.waitForCanvas(); + await this.waitForAPIResponseByPartialTextMatch('op=MetaBreakdown', LARGE_DATA_TIMEOUT); await this.waitForAllProgressBarsToDisappear(); } } @@ -329,17 +355,31 @@ export class ResourcesPage extends BasePage { /** * Selects an option from the "Categorize By" dropdown on the Resources page. - * This method uses the `categorizeBySelect` locator to select the specified option - * and optionally waits for the page to load and the canvas to update after the selection. * - * @param {string} option - The option to select from the dropdown. - * @param {boolean} [wait=true] - Whether to wait for the page to load and the canvas to update after the selection. + * If the selected option differs from the current selection, waits for the + * breakdown API response before continuing. Optionally waits for all progress + * bars to disappear after the selection. + * + * @param {string} option - The option to select from the Categorize By dropdown. + * @param {boolean} [wait=true] - Whether to wait for the API response and progress bars after selection. + * Set to `false` to skip waiting, e.g. when chaining multiple interactions. * @returns {Promise} Resolves when the option is selected and the optional wait is complete. + * + * @example + * // Select a categorize by option and wait for data to load + * await resourcesPage.selectCategorizeBy('Service name'); + * + * @example + * // Select without waiting + * await resourcesPage.selectCategorizeBy('Region', false); */ async selectCategorizeBy(option: string, wait: boolean = true): Promise { + const selectedOption = await this.selectedComboBoxOption(this.categorizeBySelect); await this.selectFromComboBox(this.categorizeBySelect, option); if (wait) { - await this.waitForCanvas(); + if (selectedOption !== option) { + await this.waitForFirstAPIResponseByPartialTextMatch(['breakdown_by', 'resources_count'], LARGE_DATA_TIMEOUT); + } await this.waitForAllProgressBarsToDisappear(); } } @@ -445,7 +485,8 @@ export class ResourcesPage extends BasePage { throw new Error('Tag must be provided'); } await this.groupByTagSelect.click(); - await this.simplePopover.getByText(tag, { exact: true }).click(); + await this.simplePopover.locator('li').last().waitFor(); + await this.simplePopover.locator(`//li[.="${tag}"]`).click(); } /** @@ -671,7 +712,7 @@ export class ResourcesPage extends BasePage { await perspectiveButton.click(); await this.perspectivesApplyBtn.click(); await this.perspectivesApplyBtn.waitFor({ state: 'hidden' }); - await this.waitForCanvas(); + await this.waitForFirstAPIResponseByPartialTextMatch(['breakdown_by', 'resources_count', 'op=MetaBreakdown'], LARGE_DATA_TIMEOUT); await this.waitForAllProgressBarsToDisappear(); } @@ -693,6 +734,8 @@ export class ResourcesPage extends BasePage { * (case-sensitive) the name of the existing perspective shown in the modal. */ getPerspectiveOverwriteMessage(perspectiveName: string): Locator { - return this.savePerspectiveSideModal.getByText(`The existing perspective (${perspectiveName}) will be overwritten with new options.`, { exact: true }); + return this.savePerspectiveSideModal.getByText(`The existing perspective (${perspectiveName}) will be overwritten with new options.`, { + exact: true, + }); } } diff --git a/e2etests/playwright.config.ts b/e2etests/playwright.config.ts index 5f7b96aef..144c12b41 100644 --- a/e2etests/playwright.config.ts +++ b/e2etests/playwright.config.ts @@ -5,6 +5,13 @@ import path from 'path'; dotenv.config({ path: path.resolve(__dirname, '.env.local') }); +/** + * Extended timeout (ms) for operations that load large amounts of data, + * such as reports, exports, or pages with many resources. + * Use this with the wait helpers in `utils/wait-utils.ts`. + */ +export const LARGE_DATA_TIMEOUT = 30000; + /** * See https://playwright.dev/docs/test-configuration. */ diff --git a/e2etests/tests/perspective-tests.spec.ts b/e2etests/tests/perspective-tests.spec.ts index fac7cfed7..c8d1e3a5d 100644 --- a/e2etests/tests/perspective-tests.spec.ts +++ b/e2etests/tests/perspective-tests.spec.ts @@ -2,6 +2,7 @@ import { test } from '../fixtures/page.fixture'; import { expect, Locator } from '@playwright/test'; import { debugLog } from '../utils/debug-logging'; +import { LARGE_DATA_TIMEOUT } from '../playwright.config'; test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@perspectives', '@slow'] }, () => { test.describe.configure({ mode: 'default' }); @@ -23,7 +24,9 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe await test.step('Select options to save as a perspective', async () => { await resourcesPage.selectFilterByText(filter, filterOption); await resourcesPage.clickExpensesTab(); - await resourcesPage.selectCategorizeBy(categorizeBy); + await resourcesPage.selectCategorizeBy(categorizeBy, false); + await resourcesPage.waitForAPIResponseByPartialTextMatch('breakdown_expenses', LARGE_DATA_TIMEOUT); + await resourcesPage.waitForCanvas(); await resourcesPage.selectGroupByTag(groupByTag); await resourcesPage.click(resourcesPage.savePerspectiveBtn); }); diff --git a/e2etests/tests/resources-tests.spec.ts b/e2etests/tests/resources-tests.spec.ts index cce0377b5..35863bafa 100644 --- a/e2etests/tests/resources-tests.spec.ts +++ b/e2etests/tests/resources-tests.spec.ts @@ -734,11 +734,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour test.beforeEach('Login admin user', async ({ resourcesPage }) => { await test.step('Login admin user', async () => { await resourcesPage.page.clock.setFixedTime(new Date('2025-07-15T14:40:00Z')); - await resourcesPage.navigateToURL('/resources'); - await resourcesPage.waitForAllProgressBarsToDisappear(); - await resourcesPage.waitForCanvas(); - await resourcesPage.resetFilters(); - await resourcesPage.firstResourceItemInTable.waitFor(); + await resourcesPage.navigateToResourcesPageAndResetFilters(); }); }); @@ -807,7 +803,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour let match: boolean; await test.step('Change categorization to Region and verify the chart', async () => { - await resourcesPage.selectCategorizeBy('Region'); + await resourcesPage.selectCategorizeBy('Region', false); await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); match = await comparePngImages(expectedPath, actualPath, diffPath); expect.soft(match).toBe(true); @@ -818,7 +814,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour diffPath = path.resolve('tests', 'downloads', 'diff-resource-expenses-chart-export.png'); await test.step('Change categorization to Resource Type and verify the chart', async () => { - await resourcesPage.selectCategorizeBy('Resource type'); + await resourcesPage.selectCategorizeBy('Resource type', false); await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); match = await comparePngImages(expectedPath, actualPath, diffPath); expect.soft(match).toBe(true); @@ -829,7 +825,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour diffPath = path.resolve('tests', 'downloads', 'diff-data-expenses-chart-export.png'); await test.step('Change categorization to Data source and verify the chart', async () => { - await resourcesPage.selectCategorizeBy('Data source'); + await resourcesPage.selectCategorizeBy('Data source', false); await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); match = await comparePngImages(expectedPath, actualPath, diffPath); expect.soft(match).toBe(true); @@ -840,7 +836,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour diffPath = path.resolve('tests', 'downloads', 'diff-owner-expenses-chart-export.png'); await test.step('Change categorization to Owner and verify the chart', async () => { - await resourcesPage.selectCategorizeBy('Owner'); + await resourcesPage.selectCategorizeBy('Owner', false); await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); match = await comparePngImages(expectedPath, actualPath, diffPath); expect.soft(match).toBe(true); @@ -851,7 +847,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour diffPath = path.resolve('tests', 'downloads', 'diff-pool-expenses-chart-export.png'); await test.step('Change categorization to Pool and verify the chart', async () => { - await resourcesPage.selectCategorizeBy('Pool'); + await resourcesPage.selectCategorizeBy('Pool', false); await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); match = await comparePngImages(expectedPath, actualPath, diffPath); expect.soft(match).toBe(true); @@ -862,7 +858,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour diffPath = path.resolve('tests', 'downloads', 'diff-k8node-expenses-chart-export.png'); await test.step('Change categorization to K8s node and verify the chart', async () => { - await resourcesPage.selectCategorizeBy('K8s node'); + await resourcesPage.selectCategorizeBy('K8s node', false); await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); match = await comparePngImages(expectedPath, actualPath, diffPath); expect.soft(match).toBe(true); @@ -873,7 +869,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour diffPath = path.resolve('tests', 'downloads', 'diff-k8sservice-expenses-chart-export.png'); await test.step('Change categorization to K8s service and verify the chart', async () => { - await resourcesPage.selectCategorizeBy('K8s service'); + await resourcesPage.selectCategorizeBy('K8s service', false); await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); match = await comparePngImages(expectedPath, actualPath, diffPath); expect.soft(match).toBe(true); @@ -884,7 +880,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour diffPath = path.resolve('tests', 'downloads', 'diff-k8snamespace-expenses-chart-export.png'); await test.step('Change categorization to K8s namespace and verify the chart', async () => { - await resourcesPage.selectCategorizeBy('K8s namespace'); + await resourcesPage.selectCategorizeBy('K8s namespace', false); await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); match = await comparePngImages(expectedPath, actualPath, diffPath); expect.soft(match).toBe(true);