Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

front: fix flaky e2e test 008 #10475

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion front/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@
"remark-gfm": "^4.0.0",
"reselect": "^5.1.0",
"uuid": "^11.0.5",
"viewport-mercator-project": "^7.0.4"
"viewport-mercator-project": "^7.0.4",
"virtua": "^0.39.3"
},
"devDependencies": {
"@apidevtools/swagger-parser": "^10.1.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useMemo, useState } from 'react';
import { useMemo, useState, useCallback } from 'react';

import cx from 'classnames';
import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { Virtualizer } from 'virtua';

import { MANAGE_TRAIN_SCHEDULE_TYPES } from 'applications/operationalStudies/consts';
import type { Conflict, InfraState, TrainScheduleResult } from 'common/api/osrdEditoastApi';
Expand Down Expand Up @@ -63,28 +64,31 @@ const Timetable = ({
setShowTrainDetails(!showTrainDetails);
};

const removeAndUnselectTrains = (trainIds: number[]) => {
const removeAndUnselectTrains = useCallback((trainIds: number[]) => {
removeTrains(trainIds);
setSelectedTrainIds([]);
dtoImport();
};
}, []);

const toggleConflictsListExpanded = () => {
setConflictsListExpanded(!conflictsListExpanded);
};

const handleSelectTrain = (id: number) => {
const currentSelectedTrainIds = [...selectedTrainIds];
const index = currentSelectedTrainIds.indexOf(id);
const handleSelectTrain = useCallback(
(id: number) => {
const currentSelectedTrainIds = [...selectedTrainIds];
const index = currentSelectedTrainIds.indexOf(id);

if (index === -1) {
currentSelectedTrainIds.push(id);
} else {
currentSelectedTrainIds.splice(index, 1);
}
if (index === -1) {
currentSelectedTrainIds.push(id);
} else {
currentSelectedTrainIds.splice(index, 1);
}

setSelectedTrainIds(currentSelectedTrainIds);
};
setSelectedTrainIds(currentSelectedTrainIds);
},
[selectedTrainIds]
);

const handleConflictClick = (conflict: Conflict) => {
if (conflict.train_ids.length > 0) {
Expand Down Expand Up @@ -145,30 +149,32 @@ const Timetable = ({
trainSchedules={trainSchedules}
isInSelection={selectedTrainIds.length > 0}
/>
{displayedTrainSchedules.map((train: TrainScheduleWithDetails, index) => (
<div key={`timetable-train-card-${train.id}`}>
{showDepartureDates[index] && (
<div className="scenario-timetable-departure-date">
{currentDepartureDates[index]}
</div>
)}
<TimetableTrainCard
isInSelection={selectedTrainIds.includes(train.id)}
handleSelectTrain={handleSelectTrain}
train={train}
isSelected={infraState === 'CACHED' && selectedTrainId === train.id}
isModified={train.id === trainIdToEdit}
setDisplayTrainScheduleManagement={setDisplayTrainScheduleManagement}
upsertTrainSchedules={upsertTrainSchedules}
setTrainIdToEdit={setTrainIdToEdit}
removeTrains={removeAndUnselectTrains}
projectionPathIsUsed={
infraState === 'CACHED' && trainIdUsedForProjection === train.id
}
dtoImport={dtoImport}
/>
</div>
))}
<Virtualizer overscan={15}>
{displayedTrainSchedules.map((train: TrainScheduleWithDetails, index) => (
<div key={`timetable-train-card-${train.id}`}>
{showDepartureDates[index] && (
<div className="scenario-timetable-departure-date">
{currentDepartureDates[index]}
</div>
)}
<TimetableTrainCard
isInSelection={selectedTrainIds.includes(train.id)}
handleSelectTrain={handleSelectTrain}
train={train}
isSelected={infraState === 'CACHED' && selectedTrainId === train.id}
isModified={train.id === trainIdToEdit}
setDisplayTrainScheduleManagement={setDisplayTrainScheduleManagement}
upsertTrainSchedules={upsertTrainSchedules}
setTrainIdToEdit={setTrainIdToEdit}
removeTrains={removeAndUnselectTrains}
projectionPathIsUsed={
infraState === 'CACHED' && trainIdUsedForProjection === train.id
}
dtoImport={dtoImport}
/>
</div>
))}
</Virtualizer>
<div
className={cx('bottom-timetables-trains', {
'empty-list': trainSchedulesWithDetails.length === 0,
Expand Down
6 changes: 3 additions & 3 deletions front/tests/011-op-times-and-stops-tab.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { getTranslations, readJsonFile, waitForInfraStateToBeCached } from './ut
import { getInfra } from './utils/api-setup';
import { cleanWhitespace, cleanWhitespaceInArray } from './utils/dataNormalizer';
import createScenario from './utils/scenario';
import scrollContainer from './utils/scrollHelper';
import { scrollHorizontally } from './utils/scrollHelper';
import { deleteScenario } from './utils/teardown-utils';
import type { StationData } from './utils/types';
import enTranslations from '../public/locales/en/timesStops.json';
Expand Down Expand Up @@ -109,7 +109,7 @@ test.describe('Times and Stops Tab Verification', () => {

// Navigate to the Times and Stops tab and scroll to the data sheet
await operationalStudiesPage.clickOnTimesAndStopsTab();
await scrollContainer(page, '.time-stops-datasheet .dsg-container');
await scrollHorizontally(page, '.time-stops-datasheet .dsg-container');
}
);

Expand Down Expand Up @@ -171,7 +171,7 @@ test.describe('Times and Stops Tab Verification', () => {
await opOutputTablePage.verifyTimesStopsDataSheetVisibility();

// Scroll and extract output table data for verification
await scrollContainer(page, '.time-stop-outputs .time-stops-datasheet .dsg-container');
await scrollHorizontally(page, '.time-stop-outputs .time-stops-datasheet .dsg-container');
await opOutputTablePage.getOutputTableData(outputExpectedCellData);
});

Expand Down
12 changes: 6 additions & 6 deletions front/tests/012-op-simulation-settings-tab.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
import { deleteApiRequest, getInfra, setElectricalProfile } from './utils/api-setup';
import { cleanWhitespace } from './utils/dataNormalizer';
import createScenario from './utils/scenario';
import scrollContainer from './utils/scrollHelper';
import { scrollHorizontally } from './utils/scrollHelper';
import { deleteScenario } from './utils/teardown-utils';
import type { StationData } from './utils/types';
import enTranslations from '../public/locales/en/timesStops.json';
Expand Down Expand Up @@ -149,7 +149,7 @@ test.describe('Simulation Settings Tab Verification', () => {
await routePage.performPathfindingByTrigram('WS', 'SES', 'MWS');
// Navigate to the Times and Stops tab and fill in required data
await operationalStudiesPage.clickOnTimesAndStopsTab();
await scrollContainer(page, '.time-stops-datasheet .dsg-container');
await scrollHorizontally(page, '.time-stops-datasheet .dsg-container');
}
);

Expand Down Expand Up @@ -193,7 +193,7 @@ test.describe('Simulation Settings Tab Verification', () => {
actionName: 'visual assertion',
}
);
await scrollContainer(page, '.time-stop-outputs .time-stops-datasheet .dsg-container');
await scrollHorizontally(page, '.time-stop-outputs .time-stops-datasheet .dsg-container');
await opOutputTablePage.getOutputTableData(expectedCellDataElectricalProfileON);
await opTimetablePage.clickOnTimetableCollapseButton();
// Deactivate electrical profiles and verify output results
Expand Down Expand Up @@ -254,7 +254,7 @@ test.describe('Simulation Settings Tab Verification', () => {
actionName: 'visual assertion',
}
);
await scrollContainer(page, '.time-stop-outputs .time-stops-datasheet .dsg-container');
await scrollHorizontally(page, '.time-stop-outputs .time-stops-datasheet .dsg-container');
await opOutputTablePage.getOutputTableData(expectedCellDataCodeCompoON);
await opTimetablePage.clickOnTimetableCollapseButton();
// Remove the composition code option and verify the changes
Expand Down Expand Up @@ -325,7 +325,7 @@ test.describe('Simulation Settings Tab Verification', () => {
actionName: 'visual assertion',
}
);
await scrollContainer(page, '.time-stop-outputs .time-stops-datasheet .dsg-container');
await scrollHorizontally(page, '.time-stop-outputs .time-stops-datasheet .dsg-container');
await opOutputTablePage.getOutputTableData(expectedCellDataLinearMargin);
await opTimetablePage.clickOnTimetableCollapseButton();
// Modify the margin to 'Mareco' and verify the changes
Expand Down Expand Up @@ -398,7 +398,7 @@ test.describe('Simulation Settings Tab Verification', () => {
actionName: 'visual assertion',
}
);
await scrollContainer(page, '.time-stop-outputs .time-stops-datasheet .dsg-container');
await scrollHorizontally(page, '.time-stop-outputs .time-stops-datasheet .dsg-container');
await opOutputTablePage.getOutputTableData(expectedCellDataForAllSettings);
});
});
3 changes: 3 additions & 0 deletions front/tests/pages/op-timetable-page-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import enTranslations from '../../public/locales/en/operationalStudies/scenario.
import frTranslations from '../../public/locales/fr/operationalStudies/scenario.json';
import { EXPLICIT_UI_STABILITY_TIMEOUT, SIMULATION_RESULT_TIMEOUT } from '../assets/timeout-const';
import { getTranslations } from '../utils';
import { scrollVertically } from '../utils/scrollHelper';

class OperationalStudiesTimetablePage extends CommonPage {
readonly invalidTrainsMessage: Locator;
Expand Down Expand Up @@ -204,10 +205,12 @@ class OperationalStudiesTimetablePage extends CommonPage {
for (let currentTrainIndex = 0; currentTrainIndex < trainCount; currentTrainIndex += 1) {
await this.page.waitForLoadState();
await this.simulationResult.waitFor();

const trainButton = OperationalStudiesTimetablePage.getTrainButton(
this.timetableTrains.nth(currentTrainIndex)
);
await trainButton.click({ position: { x: 5, y: 5 } });
await scrollVertically(this.page, '.scenario-timetable-trains');
await this.verifySimulationResultsVisibility();
}
}
Expand Down
56 changes: 53 additions & 3 deletions front/tests/utils/scrollHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface ScrollOptions {
* @param ScrollOptions - Optional scroll configuration including step size, timeout, and scroll offset threshold.
* @returns {Promise<void>} - Resolves once the container has been fully scrolled.
*/
const scrollContainer = async (
export const scrollHorizontally = async (
page: Page,
containerSelector: string,
{ stepSize = 300, timeout = 20, scrollOffsetThreshold = 200 }: ScrollOptions = {}
Expand All @@ -38,7 +38,7 @@ const scrollContainer = async (
container
);

// Exit early if there's little or no scrollable content
// Exit early if there's a little or no scrollable content
if (scrollWidth <= clientWidth + scrollOffsetThreshold) {
await container.dispose();
return;
Expand All @@ -64,4 +64,54 @@ const scrollContainer = async (
await container.dispose();
};

export default scrollContainer;
/**
* Scroll a specified container element vertically by a small step.
*
* @param page - The Playwright page object.
* @param containerSelector - The CSS selector for the scrollable container element.
* @param ScrollOptions - Optional scroll configuration including step size and scroll offset threshold.
* @returns {Promise<void>} - Resolves once a small scroll action is performed.
*/
export const scrollVertically = async (
page: Page,
containerSelector: string,
{ stepSize = 30, scrollOffsetThreshold = 47 }: ScrollOptions = {}
): Promise<void> => {
// Locate the scrollable container on the page
await page.waitForSelector(containerSelector);
const container = await page.evaluateHandle(
(selector: string) => document.querySelector(selector),
containerSelector
);

// Retrieve the scrollable height and visible height of the container
const { scrollHeight, clientHeight } = await page.evaluate(
(containerElement) =>
containerElement
? {
scrollHeight: containerElement.scrollHeight,
clientHeight: containerElement.clientHeight,
}
: { scrollHeight: 0, clientHeight: 0 },
container
);

// Exit early if there's a little or no scrollable content
if (scrollHeight <= clientHeight + scrollOffsetThreshold) {
await container.dispose();
return;
}

// Perform a small vertical scroll
await page.evaluate(
({ containerElement, step }) => {
if (containerElement) {
containerElement.scrollTop += step;
}
},
{ containerElement: container, step: stepSize }
);

// Clean up the handle after scrolling is complete
await container.dispose();
};
Loading