diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ba18c2b59..5b1650aa1 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,27 +1,59 @@ -name: Playwright Tests +name : ZenML Playwright test pipelines + on: - push: - branches: [main, master] pull_request: - branches: [main, master] + types: [opened, synchronize, ready_for_review] + jobs: - test: - timeout-minutes: 60 + run_zenml_tests: runs-on: ubuntu-latest + + env: + ZENML_ANALYTICS_OPT_IN: "false" + steps: - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: lts/* + - name: Install dependencies run: npm install -g pnpm && pnpm install + - name: Install Playwright Browsers run: pnpm exec playwright install --with-deps - - name: Run Playwright tests - run: pnpm exec playwright test + + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: "3.12" + + - name: Install ZenML + run: | + pip install --upgrade pip + pip install zenml[server] + + - name: Verify ZenML Installation + run: zenml version + + - name: Run ZenML Simple Pipeline + run: python e2e-tests/fixtures/simple-pipeline.py + + - name: ZenMl up + run: | + zenml init + zenml up + + - name: Run Playwright Tests + working-directory: e2e-tests + run: npx playwright test --workers=1 + - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: playwright-report/ retention-days: 30 + + diff --git a/e2e-tests/00-survey.spec.ts b/e2e-tests/00-survey.spec.ts new file mode 100644 index 000000000..c79918151 --- /dev/null +++ b/e2e-tests/00-survey.spec.ts @@ -0,0 +1,45 @@ +import { test } from "@playwright/test"; +import { login } from "./utils"; + +test.describe("Survey", () => { + test("Fill survey for first time", async ({ page }) => { + await login(page); + + const isVisible = await page.locator("text=Add your account details").isVisible(); + + if (!isVisible) { + return; + } + + //survey form - Step 1 + await page.fill('input[name="fullName"]', "test"); + await page.fill('input[name="email"]', "test@test.com"); + await page + .getByLabel("I want to receive news and recommendations about how to use ZenML") + .check(); + await page.click('button span:has-text("Continue")'); + await page.waitForSelector("text=What will be your primary use for ZenML?"); + + //survey form - Step 2 + await page.click('div label:has-text("Personal")'); + await page.click('button span:has-text("Continue")'); + await page.waitForSelector("text=What best describes your current situation with ZenML?"); + + //survey form - Step 3 + await page.check('label:has-text("I\'m new to MLOps and exploring")'); + await page.click('button span:has-text("Continue")'); + await page.waitForSelector("text=What is your current infrastructure?"); + + //survey form - Step 4 + await page.check('label:has-text("GCP") button'); + await page.check('label:has-text("Azure")'); + await page.check('label:has-text("Openshift")'); + await page.check('label:has-text("AWS")'); + await page.check('label:has-text("Native Kubernetes")'); + await page.click('button span:has-text("Continue")'); + await page.waitForSelector("text=Join The ZenML Slack Community"); + + //survey form - Step 5 + await page.click('button span:has-text("Skip")'); + }); +}); diff --git a/e2e-tests/01-login.spec.ts b/e2e-tests/01-login.spec.ts new file mode 100644 index 000000000..e9bb6f275 --- /dev/null +++ b/e2e-tests/01-login.spec.ts @@ -0,0 +1,8 @@ +import { test } from "@playwright/test"; +import { login } from "./utils"; + +test.describe("Login", () => { + test("Login with default username", async ({ page }) => { + await login(page); + }); +}); diff --git a/e2e-tests/02-dashboard.spec.ts b/e2e-tests/02-dashboard.spec.ts new file mode 100644 index 000000000..52186a6f7 --- /dev/null +++ b/e2e-tests/02-dashboard.spec.ts @@ -0,0 +1,32 @@ +import { test, expect } from "@playwright/test"; +import { login } from "./utils"; + +test.describe("Dashboard", () => { + test("Check dashboard and Navbar", async ({ page }) => { + await login(page); + + const overviewLink = await page.locator('a:has-text("Overview")').isVisible(); + + if (!overviewLink) { + return; + } + //Visible the navbar + await expect(page.locator('a:has-text("Pipelines")')).toBeVisible(); + await expect(page.locator('a:has-text("Models")')).toBeVisible(); + await expect(page.locator('a:has-text("Artifacts")')).toBeVisible(); + await expect(page.locator('a:has-text("Stacks")')).toBeVisible(); + + //Change the URL by clicking each nav item + await page.click('a:has-text("Pipelines")'); + await expect(page).toHaveURL(/\/pipelines\?tab=pipelines/); + + await page.click('a:has-text("Models")'); + await expect(page).toHaveURL(/\/models/); + + await page.click('a:has-text("Artifacts")'); + await expect(page).toHaveURL(/\/artifacts/); + + await page.click('a:has-text("Stacks")'); + await expect(page).toHaveURL(/\/stacks/); + }); +}); diff --git a/e2e-tests/example.spec.ts b/e2e-tests/03-example.spec.ts similarity index 100% rename from e2e-tests/example.spec.ts rename to e2e-tests/03-example.spec.ts diff --git a/e2e-tests/fixtures/simple-pipeline.py b/e2e-tests/fixtures/simple-pipeline.py new file mode 100644 index 000000000..e89f109ee --- /dev/null +++ b/e2e-tests/fixtures/simple-pipeline.py @@ -0,0 +1,22 @@ +from typing import Annotated, Tuple +from zenml import pipeline, step + + +@step +def step_1() -> Tuple[int, Annotated[int, "custom_artifact_name"]]: + return 0, 1 + + +@step +def step_2(input_0: int) -> None: + pass + + +@pipeline(enable_cache=False) +def ui_test_pipeline(): + output_0, _ = step_1() + step_2(output_0) + + +if __name__ == "__main__": + ui_test_pipeline() diff --git a/e2e-tests/utils.ts b/e2e-tests/utils.ts new file mode 100644 index 000000000..d56a07400 --- /dev/null +++ b/e2e-tests/utils.ts @@ -0,0 +1,10 @@ +import { Page, expect } from "@playwright/test"; + +// reusable login function +export async function login(page: Page) { + await page.goto("http://127.0.0.1:8237/"); + await expect(page.locator('h1:has-text("Log in to your account")')).toBeVisible(); + await page.fill('input[name="username"]', "default"); + await page.fill('input[name="password"]', ""); + await page.click('button span:has-text("Login")'); +}