fix: make Pause button pause running program

This commit is contained in:
wangdequan
2026-07-05 21:55:17 -04:00
parent 220c15295c
commit 58854f9ecf
12 changed files with 1774 additions and 126 deletions

View File

@@ -1007,6 +1007,19 @@
if (win.webRtcp5AxisSimulation.getState().runState !== "running") {
throw new Error("RESUME action did not restore running state");
}
doc.querySelector('[data-action="PAUSE"]').click();
await wait(50);
if (
win.webRtcp5AxisSimulation.getState().runState !== "paused" ||
win.webRtcp5AxisSimulation.getState().machine.interpState !== "paused"
) {
throw new Error("PAUSE action did not pause running task on direct second use");
}
doc.querySelector('[data-action="RESUME"]').click();
await wait(50);
if (win.webRtcp5AxisSimulation.getState().runState !== "running") {
throw new Error("RESUME action did not restore running state after direct second pause");
}
const tcpButton = doc.querySelector('[data-action="kins-tcp"]');
tcpButton.click();
await wait(50);

View File

@@ -143,11 +143,24 @@
if (!Array.isArray(buttonParity) || buttonParity.length < 50) {
throw new Error(`AXIS button parity list too small: ${buttonParity?.length}`);
}
for (const action of ["estop", "power", "run-ready", "run", "pause", "resume", "step", "stop", "home-all", "jog-plus", "jog-minus", "kins-identity", "kins-tcp", "kins-userk", "clear-preview"]) {
for (const action of ["estop", "power", "run-ready", "run", "pause", "resume", "pause-resume", "step", "stop", "home-all", "jog-plus", "jog-minus", "spindle-reverse", "spindle-stop", "spindle-forward", "kins-identity", "kins-tcp", "kins-userk", "clear-preview"]) {
if (!buttonParity.some((item) => item.action === action && item.sourceSymbol && item.sourceLines && item.expected)) {
throw new Error(`missing source-referenced button parity for ${action}`);
}
}
const domControls = [...doc.querySelectorAll("[data-action], [data-menu-command]")];
for (const action of [...new Set(buttonParity.map((item) => item.action))]) {
const controls = domControls.filter((control) => (
control.dataset.action === action ||
control.dataset.menuCommand === action
));
if (controls.length === 0) {
throw new Error(`button parity action has no DOM control: ${action}`);
}
if (!controls.some((control) => control.dataset.axisSourceRef && control.dataset.axisExpectedEffect)) {
throw new Error(`button parity action has no source-tagged DOM control: ${action}`);
}
}
for (const selector of [
'[data-action="estop"]',
'[data-action="power"]',
@@ -156,6 +169,9 @@
'[data-action="step"]',
'[data-action="stop"]',
'[data-action="jog-plus"]',
'[data-action="spindle-reverse"]',
'[data-action="spindle-stop"]',
'[data-action="spindle-forward"]',
'[data-action="toggle-flood"]',
'[data-action="kins-tcp"]',
'[data-action="clear-preview"]',
@@ -273,17 +289,90 @@
await click('[data-action="power"]', "AXIS power");
await waitState((state) => state.machine.powerOn === true && state.machine.taskState === "on", "machine power on");
await click('[data-action="home-all"]', "AXIS home all");
await waitState((state) => state.machine.allHomed === true && state.machine.mode === "manual", "home all");
await waitState((state) => (
state.machine.allHomed === true
&& state.machine.mode === "manual"
&& Math.abs(Number(state.axisPose.x) - 0) < 1e-6
&& Math.abs(Number(state.axisPose.y) - 0) < 1e-6
&& Math.abs(Number(state.axisPose.z) - 10) < 1e-6
&& Math.abs(Number(state.dro.x) - 0) < 1e-6
&& Math.abs(Number(state.dro.y) - 0) < 1e-6
&& Math.abs(Number(state.dro.z) - 10) < 1e-6
), "home all");
await click('[data-action="toggle-auto-manual"]', "AXIS AUTO mode toolbar toggle");
await waitState((state) => state.machine.mode === "auto", "toolbar toggle to AUTO");
await click('[data-action="toggle-auto-manual"]', "AXIS MANUAL mode toolbar toggle");
await waitState((state) => state.machine.mode === "manual", "toolbar toggle to MANUAL");
await click('[data-action="run"]', "AXIS immediate-pause run");
assert(doc.querySelector('[data-tool-id="tbtn_pause"]')?.dataset.action === "pause-resume", "toolbar pause button is LinuxCNC task_pauseresume");
await click('[data-tool-id="tbtn_pause"]', "AXIS immediate pause/resume button after run click");
await waitState((state) => (
state.runState === "paused"
&& state.machine.interpState === "paused"
&& state.machine.taskPaused === true
), "immediate pause after run click");
assert(doc.querySelector('[data-tool-id="tbtn_pause"]')?.dataset.paused === "true", "toolbar pause button visibly enters paused state");
assert(doc.querySelector('[data-tool-id="tbtn_pause"]')?.title === "Resume program", "toolbar pause button title switches to resume");
await new Promise((resolve) => setTimeout(resolve, 500));
assert(api.getState().runState === "paused", "immediate pause remains paused after async task/HAL settle");
await click('[data-action="stop"]', "AXIS stop after immediate pause");
await waitState((state) => state.runState === "stopped" && state.machine.mode === "auto", "stop after immediate pause");
await click('[data-action="tab-manual"]', "AXIS manual tab after immediate pause");
await waitState((state) => state.machine.mode === "manual", "manual mode after immediate pause");
await click('[data-action="run"]', "AXIS direct run after home");
await waitState((state) => (
(state.runState === "running" || state.runState === "complete")
&& state.machine.mode === "auto"
&& state.programRuntimeFeedback
), "direct run after power and home");
await click('[data-menu-command="pause"]', "AXIS menu Pause after direct run");
await waitState((state) => (
state.runState === "paused"
&& state.machine.interpState === "paused"
&& state.machine.taskPaused === true
), "menu Pause after direct run");
await click('[data-menu-command="resume"]', "AXIS menu Resume after direct Pause");
await waitState((state) => state.runState === "running" && state.machine.interpState === "reading", "menu Resume after direct Pause");
await click('[data-tool-id="tbtn_pause"]', "AXIS toolbar pause after direct run");
await waitState((state) => (
state.runState === "paused"
&& state.machine.interpState === "paused"
&& state.machine.taskPaused === true
), "pause after direct run");
const pauseToolbarAfterPause = doc.querySelector('[data-tool-id="tbtn_pause"]');
assert(pauseToolbarAfterPause?.dataset.action === "pause-resume", "toolbar pause button stays bound to LinuxCNC task_pauseresume while paused");
assert(pauseToolbarAfterPause?.dataset.paused === "true", "toolbar pause button has paused dataset while paused");
assert(pauseToolbarAfterPause?.getAttribute("aria-pressed") === "true", "toolbar pause button aria pressed while paused");
await click('[data-tool-id="tbtn_pause"]', "AXIS toolbar pause/resume resumes while already paused");
await waitState((state) => state.runState === "running" && state.machine.interpState === "reading", "toolbar pause/resume resumes while paused");
assert(doc.querySelector('[data-tool-id="tbtn_pause"]')?.dataset.paused === "false", "toolbar pause button leaves paused state after resume");
assert(doc.querySelector('[data-tool-id="tbtn_pause"]')?.title === "Pause program", "toolbar pause button title switches to pause after resume");
await click('[data-tool-id="tbtn_pause"]', "AXIS toolbar pause/resume pauses again");
await waitState((state) => (
state.runState === "paused"
&& state.machine.interpState === "paused"
&& state.machine.taskPaused === true
), "toolbar pause/resume pauses again");
const beforeStepSample = api.getState().programExecutionSampleIndex;
await click('[data-action="step"]', "AXIS step after pause");
await waitState((state) => (
(state.runState === "stepping" || state.runState === "paused")
&& state.machine.interpState === "paused"
&& state.machine.taskPaused === true
&& Number(state.programExecutionSampleIndex) > Number(beforeStepSample)
), "step after pause");
assert(doc.querySelector('[data-tool-id="tbtn_pause"]')?.dataset.action === "pause-resume", "toolbar pause button stays task_pauseresume after step");
await click('[data-tool-id="tbtn_pause"]', "AXIS toolbar pause/resume after pause");
await waitState((state) => state.runState === "running" && state.machine.interpState === "reading", "resume after pause");
assert(doc.querySelector('[data-tool-id="tbtn_pause"]')?.dataset.action === "pause-resume", "toolbar pause button stays task_pauseresume after resume");
await click('[data-tool-id="tbtn_pause"]', "AXIS second toolbar pause after resume");
await waitState((state) => (
state.runState === "paused"
&& state.machine.interpState === "paused"
&& state.machine.taskPaused === true
), "second pause after resume");
await click('[data-tool-id="tbtn_pause"]', "AXIS second toolbar resume after pause");
await waitState((state) => state.runState === "running" && state.machine.interpState === "reading", "second resume after pause");
await click('[data-action="stop"]', "AXIS stop after direct run");
await waitState((state) => state.runState === "stopped" && state.machine.mode === "auto", "stop after direct run");
await click('[data-action="tab-manual"]', "AXIS manual tab after direct run");
@@ -329,10 +418,38 @@
const beforeFeed = api.getState().feed.feedOverride;
await click('[data-action="feed-override-up"]', "feed override up");
await waitState((state) => state.feed.feedOverride === Math.min(beforeFeed + 10, 200), "feed override");
await click('[data-action="spindle-forward"]', "spindle forward");
await waitState((state) => state.spindle.direction === "forward" && state.spindle.enabled === true, "spindle forward");
await click('[data-action="spindle-reverse"]', "spindle reverse");
await waitState((state) => (
state.spindle.direction === "reverse"
&& state.spindle.enabled === true
&& state.linuxCncProcessMonitor.spindle.halPins.on === 1
&& state.linuxCncProcessMonitor.spindle.halPins.forward === 0
&& state.linuxCncProcessMonitor.spindle.halPins.reverse === 1
&& state.linuxCncProcessMonitor.spindle.halPins.speedOut > 0
), "spindle reverse");
await waitFor(() => doc.querySelector("[data-active-gcodes]")?.dataset.activeGcodes.includes("M4") ? true : null, "active G-codes reverse spindle");
await click('[data-action="spindle-stop"]', "spindle stop");
await waitState((state) => state.spindle.direction === "stop" && state.spindle.enabled === false, "spindle stop");
await waitState((state) => (
state.spindle.direction === "stop"
&& state.spindle.enabled === false
&& state.linuxCncProcessMonitor.spindle.halPins.on === 0
&& state.linuxCncProcessMonitor.spindle.halPins.forward === 0
&& state.linuxCncProcessMonitor.spindle.halPins.reverse === 0
&& state.linuxCncProcessMonitor.spindle.halPins.speedOut === 0
), "spindle stop");
await waitFor(() => doc.querySelector("[data-active-gcodes]")?.dataset.activeGcodes.includes("M5") ? true : null, "active G-codes stopped spindle");
await click('[data-action="spindle-forward"]', "spindle forward");
await waitState((state) => (
state.spindle.direction === "forward"
&& state.spindle.enabled === true
&& state.linuxCncProcessMonitor.spindle.halPins.on === 1
&& state.linuxCncProcessMonitor.spindle.halPins.forward === 1
&& state.linuxCncProcessMonitor.spindle.halPins.reverse === 0
&& state.linuxCncProcessMonitor.spindle.halPins.speedOut > 0
), "spindle forward");
await waitFor(() => doc.querySelector("[data-active-gcodes]")?.dataset.activeGcodes.includes("M3") ? true : null, "active G-codes forward spindle");
await click('[data-action="spindle-stop"]', "spindle stop after forward");
await waitState((state) => state.spindle.direction === "stop" && state.spindle.enabled === false, "spindle stop after forward");
await click('[data-action="toggle-flood"]', "flood coolant");
await waitState((state) => state.coolant.flood === true, "flood coolant");
await click('[data-action="block-delete"]', "block delete");