This is an automated email from the ASF dual-hosted git repository.
shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new a7e1de351a4 Remove unused HITL browser workflow helpers (#75851)
a7e1de351a4 is described below
commit a7e1de351a414aaad0d21af1ed5443029d5806fe
Author: Hussein Awala <[email protected]>
AuthorDate: Thu Oct 29 06:12:33 2026 +0100
Remove unused HITL browser workflow helpers (#75851)
---
.../ui/tests/e2e/pages/RequiredActionsPage.ts | 357 +--------------------
1 file changed, 2 insertions(+), 355 deletions(-)
diff --git a/airflow-core/src/airflow/ui/tests/e2e/pages/RequiredActionsPage.ts
b/airflow-core/src/airflow/ui/tests/e2e/pages/RequiredActionsPage.ts
index 6d03a732fe1..8705be453fc 100644
--- a/airflow-core/src/airflow/ui/tests/e2e/pages/RequiredActionsPage.ts
+++ b/airflow-core/src/airflow/ui/tests/e2e/pages/RequiredActionsPage.ts
@@ -16,29 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { type APIRequestContext, expect, type Locator, type Page } from
"@playwright/test";
-import { testConfig } from "playwright.config";
+import { expect, type Locator, type Page } from "@playwright/test";
import { HITLReviewDrawer } from "tests/e2e/components/HITLReviewDrawer";
-import { apiTriggerDagRun, waitForDagReady } from
"tests/e2e/utils/api/dag-runs";
import { BasePage } from "./BasePage";
export class RequiredActionsPage extends BasePage {
public readonly actionsTable: Locator;
- public readonly emptyStateMessage: Locator;
public readonly hitlReviewDrawer: HITLReviewDrawer;
public readonly pageHeading: Locator;
- // Standalone API context — page.request degrades after many navigations in
WebKit.
- private readonly apiRequest: APIRequestContext;
-
- public constructor(page: Page, apiRequest?: APIRequestContext) {
+ public constructor(page: Page) {
super(page);
- this.apiRequest = apiRequest ?? page.request;
this.pageHeading = page.getByRole("heading").filter({ hasText: /required
action/i });
this.actionsTable = page.getByTestId("table-list");
- this.emptyStateMessage = page.getByText(/no required actions found/i);
this.hitlReviewDrawer = new HITLReviewDrawer(page);
}
@@ -65,10 +57,6 @@ export class RequiredActionsPage extends BasePage {
.first();
}
- public async isEmptyStateDisplayed(): Promise<boolean> {
- return this.emptyStateMessage.isVisible();
- }
-
public async navigateToPendingRequiredActionsPage(): Promise<void> {
await this.navigateTo(RequiredActionsPage.getPendingRequiredActionsUrl());
await expect(this.pageHeading).toBeVisible({ timeout: 30_000 });
@@ -80,345 +68,4 @@ export class RequiredActionsPage extends BasePage {
await expect(this.pageHeading).toBeVisible();
}).toPass({ intervals: [2000], timeout: 60_000 });
}
-
- public async runHITLFlowWithApproval(dagId: string): Promise<string> {
- return this.runHITLFlow(dagId, true);
- }
-
- public async runHITLFlowWithRejection(dagId: string): Promise<string> {
- return this.runHITLFlow(dagId, false);
- }
-
- private async clickButtonAndWaitForHITLResponse(button: Locator):
Promise<void> {
- await expect(async () => {
- const responsePromise = this.page.waitForResponse(
- (res) => res.url().includes("hitlDetails") && res.request().method()
=== "PATCH",
- { timeout: 30_000 },
- );
-
- await button.click();
- const response = await responsePromise;
-
- expect(response.ok(), `HITL PATCH failed with status
${response.status()}`).toBeTruthy();
- }).toPass({ intervals: [3000, 5000], timeout: 60_000 });
- }
-
- private async clickOnTaskInGrid(dagRunId: string, taskId: string):
Promise<void> {
- const taskLocator = this.page.getByTestId(`grid-${dagRunId}-${taskId}`);
-
- await expect(async () => {
- const gridDataLoaded = this.page.waitForResponse((res) =>
res.url().includes("/grid") && res.ok(), {
- timeout: 30_000,
- });
-
- await this.page.reload({ waitUntil: "domcontentloaded" });
- await gridDataLoaded;
-
- await expect(taskLocator).toBeVisible({ timeout: 20_000 });
- await this.page.mouse.move(0, 0);
- await taskLocator.click({ timeout: 5000 });
- }).toPass({ intervals: [15_000], timeout: 120_000 });
- }
-
- private async clickRequiredActionLink(): Promise<void> {
- const requiredActionLink = this.page.getByRole("link", { name: /required
action/i });
-
- await expect(requiredActionLink).toBeVisible({ timeout: 30_000 });
-
- await requiredActionLink.click();
-
- // Use a specific URL pattern to avoid matching intermediate router states.
- await this.page.waitForURL(/\/dags\/.*\/runs\/.*\/tasks\//, { timeout:
30_000 });
- }
-
- private async handleApprovalTask(dagId: string, dagRunId: string, approve:
boolean): Promise<void> {
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Deferred",
- taskId: "valid_input_and_options",
- });
-
- await this.clickRequiredActionLink();
-
- const buttonName = approve ? "Approve" : "Reject";
- const actionButton = this.page.getByTestId(`hitl-option-${buttonName}`);
-
- await expect(actionButton).toBeVisible({ timeout: 30_000 });
-
- const informationInput = this.page.getByRole("textbox");
-
- if (await informationInput.isVisible()) {
- await informationInput.fill("Approved by test");
- }
-
- await expect(actionButton).toBeEnabled();
- await this.clickButtonAndWaitForHITLResponse(actionButton);
-
- await this.page.goto(`/dags/${dagId}/runs/${dagRunId}`);
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Success",
- taskId: "valid_input_and_options",
- });
- }
-
- private async handleBranchTask(dagId: string, dagRunId: string):
Promise<void> {
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Deferred",
- taskId: "choose_a_branch_to_run",
- });
-
- await this.clickRequiredActionLink();
-
- const branchButton = this.page.getByTestId("hitl-option-task_1");
-
- await expect(branchButton).toBeVisible({ timeout: 30_000 });
- await this.clickButtonAndWaitForHITLResponse(branchButton);
-
- await this.page.goto(`/dags/${dagId}/runs/${dagRunId}`);
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Success",
- taskId: "choose_a_branch_to_run",
- });
- }
-
- private async handleWaitForInputTask(dagId: string, dagRunId: string):
Promise<void> {
- await this.waitForTaskState(dagId, dagRunId, { expectedState: "Deferred",
taskId: "wait_for_input" });
-
- await this.clickRequiredActionLink();
-
- const informationInput = this.page.getByRole("textbox");
-
- await expect(informationInput).toBeVisible({ timeout: 30_000 });
- await informationInput.fill("test");
-
- const okButton = this.page.getByRole("button", { name: "OK" });
-
- await expect(okButton).toBeVisible();
- await this.clickButtonAndWaitForHITLResponse(okButton);
-
- await this.page.goto(`/dags/${dagId}/runs/${dagRunId}`);
- await this.waitForTaskState(dagId, dagRunId, { expectedState: "Success",
taskId: "wait_for_input" });
- }
-
- private async handleWaitForMultipleOptionsTask(dagId: string, dagRunId:
string): Promise<void> {
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Deferred",
- taskId: "wait_for_multiple_options",
- });
-
- const multiSelectContainer = this.page.getByRole("combobox", { name:
/select one or multiple values/i });
-
- await expect(async () => {
- await this.clickRequiredActionLink();
- await expect(multiSelectContainer).toBeVisible({ timeout: 15_000 });
- }).toPass({ intervals: [5000], timeout: 60_000 });
-
- await multiSelectContainer.click();
-
- await this.page.getByRole("option", { name: "option 4" }).click();
- await multiSelectContainer.click();
- await this.page.getByRole("option", { name: "option 5" }).click();
-
- const respondButton = this.page.getByRole("button", { name: "Respond" });
-
- await expect(respondButton).toBeVisible();
- await this.clickButtonAndWaitForHITLResponse(respondButton);
-
- await this.page.goto(`/dags/${dagId}/runs/${dagRunId}`);
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Success",
- taskId: "wait_for_multiple_options",
- });
- }
-
- private async handleWaitForOptionTask(dagId: string, dagRunId: string):
Promise<void> {
- await this.waitForTaskState(dagId, dagRunId, { expectedState: "Deferred",
taskId: "wait_for_option" });
-
- await this.clickRequiredActionLink();
-
- const optionButton = this.page.getByTestId("hitl-option-option 1");
-
- await expect(optionButton).toBeVisible({ timeout: 30_000 });
- await this.clickButtonAndWaitForHITLResponse(optionButton);
-
- await this.page.goto(`/dags/${dagId}/runs/${dagRunId}`);
- await this.waitForTaskState(dagId, dagRunId, { expectedState: "Success",
taskId: "wait_for_option" });
- }
-
- private async runHITLFlow(dagId: string, approve: boolean): Promise<string> {
- const { baseUrl } = testConfig.connection;
-
- await waitForDagReady(this.apiRequest, dagId);
- await this.apiRequest.patch(`${baseUrl}/api/v2/dags/${dagId}`, {
- data: { is_paused: false },
- });
- const { dagRunId } = await apiTriggerDagRun(this.apiRequest, dagId);
-
- await this.waitForDagRunState(dagId, dagRunId, "Running");
-
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Success",
- taskId: "wait_for_default_option",
- timeout: 120_000,
- });
-
- await this.handleWaitForInputTask(dagId, dagRunId);
-
- await this.handleWaitForMultipleOptionsTask(dagId, dagRunId);
-
- await this.handleWaitForOptionTask(dagId, dagRunId);
-
- await this.handleApprovalTask(dagId, dagRunId, approve);
-
- if (approve) {
- await this.handleBranchTask(dagId, dagRunId);
- }
-
- await this.verifyFinalTaskStates(dagId, dagRunId, approve);
-
- return dagRunId;
- }
-
- private async verifyFinalTaskStates(dagId: string, dagRunId: string,
approved: boolean): Promise<void> {
- await this.page.goto(`/dags/${dagId}/runs/${dagRunId}`);
-
- if (approved) {
- await this.waitForTaskState(dagId, dagRunId, { expectedState: "Success",
taskId: "task_1" });
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Skipped",
- taskId: "task_2",
- timeout: 30_000,
- });
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Skipped",
- taskId: "task_3",
- timeout: 30_000,
- });
- } else {
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Skipped",
- taskId: "choose_a_branch_to_run",
- timeout: 30_000,
- });
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Skipped",
- taskId: "task_1",
- timeout: 30_000,
- });
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Skipped",
- taskId: "task_2",
- timeout: 30_000,
- });
- await this.waitForTaskState(dagId, dagRunId, {
- expectedState: "Skipped",
- taskId: "task_3",
- timeout: 30_000,
- });
- }
-
- await this.navigateToRequiredActionsPage();
- await expect(this.actionsTable).toBeVisible();
- }
-
- private async waitForDagRunState(dagId: string, runId: string,
expectedState: string): Promise<void> {
- const { baseUrl } = testConfig.connection;
-
- await expect
- .poll(
- async () => {
- try {
- const response = await
this.apiRequest.get(`${baseUrl}/api/v2/dags/${dagId}/dagRuns/${runId}`, {
- timeout: 30_000,
- });
-
- if (!response.ok()) {
- return "unknown";
- }
-
- const data = (await response.json()) as { state: string };
-
- return data.state;
- } catch {
- // Transient network/timeout errors — let expect.poll retry.
- return "unknown";
- }
- },
- {
- intervals: [5000],
- message: `Dag run ${runId} did not reach state "${expectedState}"`,
- timeout: 120_000,
- },
- )
- .toBe(expectedState.toLowerCase());
-
- // toPass handles transient states between API response and page render.
- await this.page.goto(`/dags/${dagId}/runs/${runId}`);
- const detailsPanel = this.page.locator("#details-panel");
- const stateBadge = detailsPanel.getByTestId("state-badge").first();
-
- await expect(async () => {
- await expect(stateBadge).toBeVisible({ timeout: 5000 });
- await expect(stateBadge).toContainText(new RegExp(expectedState, "i"), {
timeout: 5000 });
- }).toPass({ intervals: [2000], timeout: 30_000 });
- }
-
- private async waitForTaskState(
- dagId: string,
- dagRunId: string,
- options: { expectedState: string; taskId: string; timeout?: number },
- ): Promise<void> {
- const { baseUrl } = testConfig.connection;
-
- const terminalStates = new Set(["success", "failed", "skipped", "removed",
"upstream_failed"]);
-
- await expect
- .poll(
- async () => {
- try {
- const response = await this.apiRequest.get(
-
`${baseUrl}/api/v2/dags/${dagId}/dagRuns/${dagRunId}/taskInstances/${options.taskId}`,
- { timeout: 30_000 },
- );
-
- if (!response.ok()) {
- return "unknown";
- }
-
- const data = (await response.json()) as { state: string };
- const { state } = data;
- const expected = options.expectedState.toLowerCase();
-
- if (state !== expected && terminalStates.has(state)) {
- throw new Error(
- `Task ${options.taskId} reached terminal state "${state}"
instead of "${expected}"`,
- );
- }
-
- return state;
- } catch (error) {
- if (error instanceof Error && error.message.includes("terminal
state")) {
- throw error;
- }
-
- return "unknown";
- }
- },
- {
- intervals: [5000],
- message: `Task ${options.taskId} did not reach state
"${options.expectedState}"`,
- timeout: options.timeout ?? 120_000,
- },
- )
- .toBe(options.expectedState.toLowerCase());
-
- await this.clickOnTaskInGrid(dagRunId, options.taskId);
-
- const detailsPanel = this.page.locator("#details-panel");
- const stateBadge = detailsPanel.getByTestId("state-badge").first();
-
- await expect(async () => {
- await expect(stateBadge).toBeVisible({ timeout: 5000 });
- await expect(stateBadge).toContainText(new RegExp(options.expectedState,
"i"), { timeout: 5000 });
- }).toPass({ intervals: [2000], timeout: 30_000 });
- }
}