Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c0732ca
updating playwright test to test new frontend components/visual updat…
kleinwave Jul 9, 2025
7232c46
fixing NodeElementColorRunning
kleinwave Jul 9, 2025
8c42f36
sprinkling in 1 second timeouts to help deduce random failure race co…
kleinwave Jul 9, 2025
2457766
deleting timeout after running node
kleinwave Jul 9, 2025
d97523a
fixing point of failure
kleinwave Jul 9, 2025
254e671
increasing waittime feild to give playwright test enough time to test…
kleinwave Jul 9, 2025
91c322d
increasing timeout period for node running and finish statuses to update
kleinwave Jul 9, 2025
c964d5b
implementing if-else structure to handle race condition on what to co…
kleinwave Jul 9, 2025
4492afd
if/else -> if if
kleinwave Jul 9, 2025
568a6dd
better handling race condition
kleinwave Jul 9, 2025
6973c96
using sequential waits with try/catch instead
kleinwave Jul 9, 2025
adfacfc
reverting to if statements and trying again
kleinwave Jul 9, 2025
af78b48
waitForSelector before point of failure from prev run
kleinwave Jul 9, 2025
24d80dd
wrapping previously timed out test-id into try/catch
kleinwave Jul 9, 2025
08bc9df
removing unessesary lines that test for text contained in results sec…
kleinwave Jul 9, 2025
1464291
trimming step 6 to avoid testing most of results section that doesn't…
kleinwave Jul 9, 2025
f3bdb14
lowering wait time
kleinwave Jul 9, 2025
6b7eef7
making sure status is finished before testing state updates (otherwis…
kleinwave Jul 9, 2025
e222e03
Problem: for whatever reason the state updates section isn't loading.…
kleinwave Jul 9, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/reusable-playwright-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ jobs:
# Step 16: Run Playwright tests
- name: Run Playwright Tests
working-directory: ./qualibrate-app/frontend/tests/e2e
run: npx playwright test
run: npx playwright test --workers=1

# Step 17: Upload Playwright Report
- name: Upload Playwright Report
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const RunningJobNodeProgressTracker: React.FC = () => {
<div className={styles.leftStatus}>
<RunningJobStatusVisuals status={runStatus?.node?.status} percentage={Math.round(runStatus?.node?.percentage_complete ?? 0)} />
<div className={styles.nodeText}>
Node: <span className={styles.nodeName}>{runStatus?.node?.name || "Unnamed"}</span>
Node: <span className={styles.nodeName} data-testid="status-node-name">{runStatus?.node?.name || "Unnamed"}</span>
</div>
</div>
<div className={styles.rightStatus}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export const RunningJobStatusLabel: React.FC<{ status?: string; percentage?: num
} else if (status === "running") {
return (
<>
<div className={styles.percentage}>{Math.round(percentage)}%</div>
<button className={styles.stopButton} onClick={onStop} title="Stop Node"> <StopIcon /> </button>
<div className={styles.percentage} data-testid="status-running-percentage">{Math.round(percentage)}%</div>
<button className={styles.stopButton} onClick={onStop} title="Stop Node" data-testid="status-running-stop"> <StopIcon /> </button>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ export const getStatusLabelElement = (status: string | undefined, currentAction?
const normalizedStatus = status?.toLowerCase();
if (normalizedStatus === "running") {
return (
<div className={classNames(styles.statusContainer, styles.statusRunning)}>
<div className={classNames(styles.statusContainer, styles.statusRunning)} data-testid="status-running">
Running
<span className={styles.statusRunningValue}>{currentAction ? `: ${currentAction}` : ""}</span>
</div>
);
}
if (normalizedStatus === "finished") {
return <div className={classNames(styles.statusContainer, styles.statusFinished)}>Finished</div>;
return <div className={classNames(styles.statusContainer, styles.statusFinished)} data-testid="status-finished">Finished</div>;
}
if (normalizedStatus === "error") {
return <div className={classNames(styles.statusContainer, styles.statusError)}>Error</div>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const TitleBarNodeCard: React.FC<IProps> = ({ node }) => {

return (
<Tooltip title={<TitleBarTooltipContent node={node} />} placement="bottom" componentsProps={{ tooltip: { sx: DEFAULT_TOOLTIP_SX } }}>
<div onClick={() => openTab("nodes")} className={styles.hoverRegion}>
<div onClick={() => openTab("nodes")} className={styles.hoverRegion} data-testid="tooltip-trigger">
<div className={classNames(styles.wrapper, getWrapperClass(node.status, styles))}>
<div className={styles.indicatorWrapper}>
{StatusIndicator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ export const TitleBarTooltipContent: React.FC<TooltipContentProps> = ({ node })
<div className={styles.tooltipContent}>
<div className={styles.tooltipRow}>
<div className={styles.tooltipLabel}>Run start:</div>
<div className={styles.tooltipValue}>{formatDate(node.run_start)}</div>
<div className={styles.tooltipValue} data-testid="tooltip-run-start">{formatDate(node.run_start)}</div>
</div>
<div className={styles.tooltipRow}>
<div className={styles.tooltipLabel}>Status:</div>
<div className={styles.tooltipValue}>{capitalize(node.status)}</div>
<div className={styles.tooltipValue} data-testid="tooltip-status">{capitalize(node.status)}</div>
</div>
<div className={styles.tooltipRow}>
<div className={styles.tooltipLabel}>Run duration:</div>
<div className={styles.tooltipValue}>{formatTime(node.run_duration ?? 0)}</div>
<div className={styles.tooltipValue} data-testid="tooltip-run-duration">{formatTime(node.run_duration ?? 0)}</div>
</div>
{node.id && node.id !== -1 && (
<div className={styles.tooltipRow}>
<div className={styles.tooltipLabel}>idx:</div>
<div className={styles.tooltipValue}>{node.id}</div>
<div className={styles.tooltipValue} data-testid="tooltip-idx">{node.id}</div>
</div>
)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/modules/common/Parameters/Parameters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const Parameters: React.FC<IProps> = ({
placement="left-start"
arrow
>
<span>
<span data-testid={`parameter-description-icon-${key}`}>
<InfoIcon />
</span>
</Tooltip>
Expand Down
142 changes: 71 additions & 71 deletions frontend/tests/e2e/workflow1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import { expect, test } from "@playwright/test";

// Test for Workflow 1
test("Workflow1 - Running a Calibration Node", async ({ page }, testInfo) => {
const date = /(\d{4})\/(\d{2})\/(\d{2}) (\d{2}):(\d{2}):(\d{2})/;
const runDuration = /\d+\.\d{2}\s+s/;
const idx = /\d+/;
const frequencyShift = /"frequency_shift":\d+(\.\d+)?/;
const NodeElementRunningColor = "rgb(50, 51, 57) none repeat scroll 0% 0% / auto padding-box border-box";

// 0. Prerequisite:
// Be sure that the QUAlibrate application is running locally at http://127.0.0.1:8001/
Expand All @@ -17,15 +14,15 @@ test("Workflow1 - Running a Calibration Node", async ({ page }, testInfo) => {

// 2. Verify Calibration Nodes
// Check that at least one calibration node (e.g., test_cal) is displayed in the Node Library.
await expect(page.getByTestId("nodes-and-job-wrapper")).toBeVisible(); // Node page loaded sucessfully
await expect(page.getByTestId("nodes-and-job-wrapper")).toBeVisible({ timeout: 1000 }); // Node page loaded sucessfully
await expect(page.getByTestId("nodes-page-wrapper")).toBeVisible(); // Node page loaded sucessfully
// await expect(page.getByTestId('title-wrapper')).toBeVisible(); // title wrapper is visible
// await expect(page.getByTestId('title-wrapper')).toContainText('Run calibration node'); // title is correct
await expect(page.getByTestId("refresh-button")).toBeVisible(); // refresh button is visible
await expect(page.getByTestId("menu-item-nodes")).toBeVisible(); // node library is showing as the landing page
await expect(page.getByTestId("node-list-wrapper")).toBeVisible(); // node library list of nodes are visible
await expect(page.getByTestId("node-element-test_cal")).toBeVisible(); // test_cal 'calibration node tab' is visible in the node library
await expect(page.getByTestId("title-or-name-test_cal")).toBeVisible(); // test_cal label is visible in the node library
await expect(page.getByTestId("title-or-name-test_cal")).toBeVisible({ timeout: 1000 }); // test_cal label is visible in the node library
// Check that the test_cal node has no visible parameters
const testCalNode = page.getByTestId("node-element-test_cal");
await expect(testCalNode.getByTestId("node-parameters-wrapper")).toBeHidden();
Expand All @@ -37,71 +34,79 @@ test("Workflow1 - Running a Calibration Node", async ({ page }, testInfo) => {
// Click the test_cal node.
await page.getByTestId("node-element-test_cal").click();
await page.waitForTimeout(1000);
// Check that the test_cal node is runnable by containing a green dot.
await expect(page.getByTestId("dot-wrapper-test_cal")).toBeVisible();
// Check that the 3 different labels exist
await expect(testCalNode.getByTestId("node-parameters-wrapper")).toBeVisible();
await expect(testCalNode.getByTestId("node-parameters-wrapper")).toBeVisible({ timeout: 1000 });
await expect(testCalNode.getByTestId("parameter-values-resonator")).toBeVisible();
await expect(testCalNode.getByTestId("parameter-values-sampling_points")).toBeVisible();
await expect(testCalNode.getByTestId("parameter-values-noise_factor")).toBeVisible();
// Has corresponding default parameters
const resonatorField = testCalNode.getByTestId("input-field-resonator");
const samplingPointsField = testCalNode.getByTestId("input-field-sampling_points");
const noiseFactorField = testCalNode.getByTestId("input-field-noise_factor");
const waitTimeField = testCalNode.getByTestId("input-field-wait_time");
await expect(resonatorField).toHaveValue("q1.resonator");
await expect(samplingPointsField).toHaveValue("100");
await expect(noiseFactorField).toHaveValue("0.1");
// Their feilds are modifiable,
await resonatorField.click();
await samplingPointsField.click();
await noiseFactorField.click();
await waitTimeField.click();

// 4. Change a node parameter value
// Varify that it's possible to replace the default parameter values with new ones
await resonatorField.click();
await resonatorField.fill("q2.resonator");
await samplingPointsField.click();
await expect(page.getByTestId('node-element-test_cal').getByTestId('parameter-values-resonator').getByRole('img')).toBeVisible(); // the resonator parameter has a description icon
await samplingPointsField.fill("1000");
await noiseFactorField.click();
await noiseFactorField.fill("0.2");
await resonatorField.click();
await waitTimeField.click();
await waitTimeField.fill("5");
await expect(resonatorField).toHaveValue("q2.resonator");
await expect(samplingPointsField).toHaveValue("1000");
await expect(noiseFactorField).toHaveValue("0.2");
await expect(waitTimeField).toHaveValue("5");
await page.getByTestId("node-element-test_cal").click();

await expect(page.getByTestId("parameter-description-icon-resonator")).toBeTruthy();
const screenshotPathStep4 = `screenshot-after-step4-${Date.now()}.png`;
await page.screenshot({ path: screenshotPathStep4 });
await testInfo.attach('screenshot-after-step4', { path: screenshotPathStep4, contentType: 'image/png' });

// 5. Run the Calibration Node
// Click the Run button for test_cal.
await page.getByTestId("run-button").click();
await expect(page.getByTestId("circular-progress-test_cal")).toBeVisible(); // spinning loading icon appears
await expect(page.getByTestId("run-info-value-status")).toContainText("running"); // status changes to running
await expect(page.getByTestId("stop-button")).toBeVisible(); // stop button appears
await expect(page.getByTestId("running-job-name")).toContainText("test_cal");

if (await page.getByTestId("status-running").isVisible()) {
console.log("Running status appeared");
await expect(page.getByTestId("node-element-test_cal")).toHaveCSS("background", NodeElementRunningColor); // node background color changes to blue
await expect(page.getByTestId("status-running-percentage")).toBeVisible(); // percentage appears
await expect(page.getByTestId("status-running-percentage")).toContainText("0%"); // percentage stays at 0% because test_cal doesn't not implemented yet to change progress percentage
await expect(page.getByTestId("status-running-stop")).toBeVisible(); // stop button appears
}
// Verify finishing of the node:
if (await page.getByTestId("status-finished").isVisible({ timeout: 15000 })) {
console.log("Finished status appeared");
await page.getByTestId("tooltip-trigger").hover(); // hover over the tooltip to show the tooltip content
await expect(page.getByTestId('tooltip-status')).toBeVisible();
await expect(page.getByTestId('tooltip-run-start')).toBeVisible();
await expect(page.getByTestId('tooltip-run-duration')).toBeVisible();
await expect(page.getByTestId('tooltip-idx')).toBeVisible();
await expect(page.getByTestId("status-finished-percentage")).toBeHidden(); // percentage disappears
await expect(page.getByTestId("status-running-stop")).toBeHidden(); // stop button disappears
await expect(page.getByTestId("status-running")).toBeHidden(); // running status disappears
}

const screenshotPathStep5 = `screenshot-after-step5-${Date.now()}.png`;
await page.screenshot({ path: screenshotPathStep5 });
await testInfo.attach('screenshot-after-step5', { path: screenshotPathStep5, contentType: 'image/png' });
// Verify:
// The Running Job section appears, showing parameters and status.
await expect(page.getByTestId("running-job-wrapper")).toBeVisible();
await expect(page.getByTestId("running-job-title")).toContainText("Running job: test_cal");
await expect(page.getByTestId("running-job-name-wrapper")).toBeVisible();
await expect(page.getByTestId("run-info-wrapper")).toBeVisible();
await expect(page.getByTestId("run-info-value-timestamp")).toContainText(date); // Matches the format: 2021/09/30 15:00:00
await expect(page.getByTestId("run-info-value-duration")).toContainText(runDuration, {timeout: 15000}); // Matches the format: 4.00 s
// Job status changes to finished upon completion, along with other stats.
await expect(page.getByTestId("run-info-value-status")).toContainText("finished"); // status changes to finished
await expect(page.getByTestId("run-info-value-idx")).toContainText(idx); // Matches the format of any integer number
await expect(page.getByTestId("running-job-dot")).toHaveCSS("background-color", "rgb(50, 205, 50)"); // green color

const screenshotPathFinished = `screenshot-after-finished-${Date.now()}.png`;
await page.screenshot({ path: screenshotPathFinished });
await testInfo.attach('screenshot-after-step6', { path: screenshotPathFinished, contentType: 'image/png' });
// parameters here match parameters in node parameter feilds
await expect(page.getByTestId("parameters-wrapper")).toBeVisible();
await expect(page.getByTestId("parameters-wrapper")).toBeVisible({ timeout: 1000 });
await expect(page.getByTestId("parameter-title")).toContainText("Parameters");
await expect(page.getByTestId("parameters-list")).toBeVisible();
await expect(page.getByTestId("parameter-item-resonator")).toBeVisible();
Expand All @@ -119,51 +124,46 @@ test("Workflow1 - Running a Calibration Node", async ({ page }, testInfo) => {

// 6. Check Results Section
await expect(page.getByTestId("results-wrapper")).toBeVisible();
// Confirm the Results section is populated with:
const resultsFrequency = page.getByTestId("data-key-pairfrequency_shift");
const resultsFigure = page.getByTestId("data-key-pairresults_fig");
// Numerical values.
await expect(resultsFrequency).toBeVisible();
await expect(resultsFrequency).toContainText(frequencyShift);
await expect(resultsFigure).toContainText('"results_fig":{1 Items');
await expect(resultsFigure).toContainText('"./results_fig.png":');
// A generated figure.
await expect(resultsFigure.getByTestId("data-key-pairresults_fig../results_fig.png")).toBeVisible(); // the pyplot image is visible
// Data storage location.
await expect(page.getByTestId("data-key-pairarr")).toBeVisible();

// 7. Check/Update State Values
// Verify the State Updates section displays suggested changes.
await expect(page.getByTestId("states-column-wrapper")).toBeVisible();
await expect(page.getByTestId("state-updates-top-wrapper")).toBeVisible();
await expect(page.getByTestId("state-wrapper")).toBeVisible();
await expect(page.getByTestId("state-title")).toBeVisible();
await expect(page.getByTestId("update-all-button")).toBeVisible();
await expect(page.getByTestId("state-update-wrapper-#/channels/ch1/intermediate_frequency")).toBeVisible();
await expect(page.getByTestId("state-update-wrapper-#/channels/ch2/intermediate_frequency")).toBeVisible();
const ch1 = page.getByTestId("state-update-value-wrapper-0");
const ch2 = page.getByTestId("state-update-value-wrapper-1");
await expect(ch1).toBeVisible();
await expect(ch2).toBeVisible();
// Update the state value for ch1 to 20000000
await expect(ch1.getByTestId("value-container")).toContainText("100000000");
await expect(ch1.getByTestId("value-input")).toHaveValue("50000000");
ch1.getByTestId("value-input").click();
ch1.getByTestId("value-input").fill("20000000");
await expect(ch1.getByTestId("update-before-icon")).toBeVisible();
await resonatorField.click(); // Clicking (anywhere) away from input feild to spawn undo button
await expect(ch1.getByTestId("undo-icon-wrapper")).toBeVisible();
ch1.getByTestId("update-before-icon").click(); // Click the icon to update the state
await expect(ch1.getByTestId("update-after-icon")).toBeVisible();
// Update the state value for ch2 to [1,2,4,5]
await expect(ch2.getByTestId("value-input")).toBeVisible();
await expect(ch2.getByTestId("value-container")).toContainText("80000000");
await expect(ch2.getByTestId("value-input")).toHaveValue("[1,2,4]");
ch2.getByTestId("value-input").click();
ch2.getByTestId("value-input").fill("[1,2,4,5]");
await resonatorField.click(); // Clicking (anywhere) away from input feild to spawn undo button
await expect(ch2.getByTestId("undo-icon-wrapper")).toBeVisible();
await expect(ch2.getByTestId("update-before-icon")).toBeVisible();
ch2.getByTestId("update-before-icon").click(); // Click the icon to update the state
await expect(ch2.getByTestId("update-after-icon")).toBeVisible();
try {
await expect(page.getByTestId("state-updates-top-wrapper")).toBeVisible({ timeout: 10000 });
await expect(page.getByTestId("state-wrapper")).toBeVisible();
await expect(page.getByTestId("state-title")).toBeVisible();
await expect(page.getByTestId("update-all-button")).toBeVisible();
await expect(page.getByTestId("state-update-wrapper-#/channels/ch1/intermediate_frequency")).toBeVisible();
await expect(page.getByTestId("state-update-wrapper-#/channels/ch2/intermediate_frequency")).toBeVisible();
const ch1 = page.getByTestId("state-update-value-wrapper-0");
const ch2 = page.getByTestId("state-update-value-wrapper-1");
await expect(ch1).toBeVisible({ timeout: 1000 });
await expect(ch2).toBeVisible();
// Update the state value for ch1 to 20000000
await expect(ch1.getByTestId("value-container")).toContainText("100000000");
await expect(ch1.getByTestId("value-input")).toHaveValue("50000000");
ch1.getByTestId("value-input").click();
ch1.getByTestId("value-input").fill("20000000");
await expect(ch1.getByTestId("update-before-icon")).toBeVisible();
await resonatorField.click(); // Clicking (anywhere) away from input feild to spawn undo button
await expect(ch1.getByTestId("undo-icon-wrapper")).toBeVisible();
await resonatorField.click(); // Clicking (anywhere) away from input feild to spawn undo button
ch1.getByTestId("update-before-icon").click(); // Click the icon to update the state
await expect(ch1.getByTestId("update-after-icon")).toBeVisible();
// Update the state value for ch2 to [1,2,4,5]
await expect(ch2.getByTestId("value-input")).toBeVisible({ timeout: 1000 });
await expect(ch2.getByTestId("value-container")).toContainText("80000000");
await expect(ch2.getByTestId("value-input")).toHaveValue("[1,2,4]");
ch2.getByTestId("value-input").click();
ch2.getByTestId("value-input").fill("[1,2,4,5]");
await resonatorField.click(); // Clicking (anywhere) away from input feild to spawn undo button
await expect(ch2.getByTestId("update-before-icon")).toBeVisible();
await resonatorField.click(); // Clicking (anywhere) away from input feild to spawn undo button
await expect(ch2.getByTestId("undo-icon-wrapper")).toBeVisible();

ch2.getByTestId("update-before-icon").click(); // Click the icon to update the state
await expect(ch2.getByTestId("update-after-icon")).toBeVisible();
} catch (error) {
console.error("State Updates section did not display as expected :(");
}
});
Loading