diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7c58dce1e5..084d4c5983 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -472,11 +472,19 @@ jobs: - run: pnpm playwright:install working-directory: site - - run: pnpm playwright:test --workers 1 + # Run tests that don't require an enterprise license without an enterprise license + - run: pnpm playwright:test --forbid-only --workers 1 env: DEBUG: pw:api working-directory: site + # Run all of the tests with an enterprise license + - run: pnpm playwright:test --forbid-only --workers 1 + env: + DEBUG: pw:api + CODER_E2E_ENTERPRISE_LICENSE: ${{ secrets.CODER_E2E_ENTERPRISE_LICENSE }} + working-directory: site + - name: Upload Playwright Failed Tests if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork uses: actions/upload-artifact@v4 diff --git a/scripts/remote_playwright.sh b/scripts/remote_playwright.sh index 5d7f35ea58..92cce9b69d 100755 --- a/scripts/remote_playwright.sh +++ b/scripts/remote_playwright.sh @@ -3,7 +3,7 @@ set -euo pipefail workspace=${1:-} coder_repo=${2:-.} -port=${3:-3000} +port=${3:-3111} if [[ -z "${workspace}" ]]; then echo "Usage: $0 [workspace coder/coder dir] [e2e port]" diff --git a/site/e2e/constants.ts b/site/e2e/constants.ts index 69c95fda10..8b4fbe50d5 100644 --- a/site/e2e/constants.ts +++ b/site/e2e/constants.ts @@ -1,3 +1,7 @@ +import * as path from "path"; + +export const coderMain = path.join(__dirname, "../../enterprise/cmd/coder"); + // Default port from the server export const coderPort = process.env.CODER_E2E_PORT ? Number(process.env.CODER_E2E_PORT) @@ -28,3 +32,5 @@ export const gitAuth = { validatePath: "/validate", installationsPath: "/installations", }; + +export const enterpriseLicense = process.env.CODER_E2E_ENTERPRISE_LICENSE ?? ""; diff --git a/site/e2e/global.setup.ts b/site/e2e/global.setup.ts index 9336791f35..c8fffff5d8 100644 --- a/site/e2e/global.setup.ts +++ b/site/e2e/global.setup.ts @@ -6,6 +6,7 @@ import { storageState } from "./playwright.config"; test("setup first user", async ({ page }) => { await page.goto("/", { waitUntil: "domcontentloaded" }); + // Setup first user await page.getByLabel(Language.usernameLabel).fill(constants.username); await page.getByLabel(Language.emailLabel).fill(constants.email); await page.getByLabel(Language.passwordLabel).fill(constants.password); @@ -15,4 +16,15 @@ test("setup first user", async ({ page }) => { await page.context().storageState({ path: storageState }); await page.getByTestId("button-select-template").isVisible(); + + // Setup license + if (constants.enterpriseLicense) { + await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" }); + + await page.getByText("Add a license").click(); + await page.getByRole("textbox").fill(constants.enterpriseLicense); + await page.getByText("Upload License").click(); + + await page.getByText("You have successfully added a license").isVisible(); + } }); diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 7bf50b1c6d..0f68de3e07 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -1,4 +1,4 @@ -import { type BrowserContext, expect, type Page } from "@playwright/test"; +import { type BrowserContext, expect, type Page, test } from "@playwright/test"; import axios from "axios"; import { type ChildProcess, exec, spawn } from "child_process"; import { randomUUID } from "crypto"; @@ -12,7 +12,13 @@ import type { UpdateTemplateMeta, } from "api/typesGenerated"; import { TarWriter } from "utils/tar"; -import { agentPProfPort, coderPort, prometheusPort } from "./constants"; +import { + agentPProfPort, + coderMain, + coderPort, + enterpriseLicense, + prometheusPort, +} from "./constants"; import { Agent, type App, @@ -25,6 +31,11 @@ import { type RichParameter, } from "./provisionerGenerated"; +// requiresEnterpriseLicense will skip the test if we're not running with an enterprise license +export function requiresEnterpriseLicense() { + test.skip(!enterpriseLicense); +} + // createWorkspace creates a workspace for a template. // It does not wait for it to be running, but it does navigate to the page. export const createWorkspace = async ( @@ -147,7 +158,7 @@ export const sshIntoWorkspace = async ( binaryArgs: string[] = [], ): Promise => { if (binaryPath === "go") { - binaryArgs = ["run", coderMainPath()]; + binaryArgs = ["run", coderMain]; } const sessionToken = await findSessionToken(page); return new Promise((resolve, reject) => { @@ -229,7 +240,7 @@ export const startAgent = async ( page: Page, token: string, ): Promise => { - return startAgentWithCommand(page, token, "go", "run", coderMainPath()); + return startAgentWithCommand(page, token, "go", "run", coderMain); }; // downloadCoderVersion downloads the version provided into a temporary dir and @@ -358,18 +369,6 @@ const waitUntilUrlIsNotResponding = async (url: string) => { ); }; -const coderMainPath = (): string => { - return path.join( - __dirname, - "..", - "..", - "enterprise", - "cmd", - "coder", - "main.go", - ); -}; - // Allows users to more easily define properties they want for agents and resources! type RecursivePartial = { [P in keyof T]?: T[P] extends (infer U)[] @@ -686,7 +685,7 @@ export const updateTemplate = async ( "go", [ "run", - coderMainPath(), + coderMain, "templates", "push", "--test.provisioner", diff --git a/site/e2e/playwright.config.ts b/site/e2e/playwright.config.ts index bdf80adf83..299c0963a6 100644 --- a/site/e2e/playwright.config.ts +++ b/site/e2e/playwright.config.ts @@ -1,11 +1,9 @@ import { defineConfig } from "@playwright/test"; -import path from "path"; -import { coderPort, coderdPProfPort, gitAuth } from "./constants"; +import * as path from "path"; +import { coderMain, coderPort, coderdPProfPort, gitAuth } from "./constants"; export const wsEndpoint = process.env.CODER_E2E_WS_ENDPOINT; -const coderMain = path.join(__dirname, "../../enterprise/cmd/coder"); - // This is where auth cookies are stored! export const storageState = path.join(__dirname, ".auth.json"); @@ -16,16 +14,14 @@ const localURL = (port: number, path: string): string => { export default defineConfig({ projects: [ { - name: "setup", + name: "testsSetup", testMatch: /global.setup\.ts/, }, { name: "tests", testMatch: /.*\.spec\.ts/, - dependencies: ["setup"], - use: { - storageState: storageState, - }, + dependencies: ["testsSetup"], + use: { storageState }, timeout: 60_000, }, ], diff --git a/site/e2e/tests/updateTemplate.spec.ts b/site/e2e/tests/updateTemplate.spec.ts index a1efdd3805..06cf88a096 100644 --- a/site/e2e/tests/updateTemplate.spec.ts +++ b/site/e2e/tests/updateTemplate.spec.ts @@ -1,5 +1,9 @@ -import { test } from "@playwright/test"; -import { createTemplate, updateTemplateSettings } from "../helpers"; +import { expect, test } from "@playwright/test"; +import { + createTemplate, + requiresEnterpriseLicense, + updateTemplateSettings, +} from "../helpers"; test("template update with new name redirects on successful submit", async ({ page, @@ -10,3 +14,24 @@ test("template update with new name redirects on successful submit", async ({ name: "new-name", }); }); + +test("require latest version", async ({ page }) => { + requiresEnterpriseLicense(); + + const templateName = await createTemplate(page); + + await page.goto(`/templates/${templateName}/settings`, { + waitUntil: "domcontentloaded", + }); + await expect(page).toHaveURL(`/templates/${templateName}/settings`); + let checkbox = await page.waitForSelector("#require_active_version"); + await checkbox.click(); + await page.getByTestId("form-submit").click(); + + await page.goto(`/templates/${templateName}/settings`, { + waitUntil: "domcontentloaded", + }); + checkbox = await page.waitForSelector("#require_active_version"); + await checkbox.scrollIntoViewIfNeeded(); + expect(await checkbox.isChecked()).toBe(true); +}); diff --git a/site/package.json b/site/package.json index 27d7196cbe..52a3795527 100644 --- a/site/package.json +++ b/site/package.json @@ -95,7 +95,7 @@ }, "devDependencies": { "@octokit/types": "12.3.0", - "@playwright/test": "1.39.0", + "@playwright/test": "1.42.1", "@storybook/addon-actions": "7.6.11", "@storybook/addon-essentials": "7.6.11", "@storybook/addon-interactions": "7.6.11", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index b1757bbd83..7b82418883 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -204,8 +204,8 @@ devDependencies: specifier: 12.3.0 version: 12.3.0 '@playwright/test': - specifier: 1.39.0 - version: 1.39.0 + specifier: 1.42.1 + version: 1.42.1 '@storybook/addon-actions': specifier: 7.6.11 version: 7.6.11 @@ -3064,12 +3064,12 @@ packages: dev: true optional: true - /@playwright/test@1.39.0: - resolution: {integrity: sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==} + /@playwright/test@1.42.1: + resolution: {integrity: sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==} engines: {node: '>=16'} hasBin: true dependencies: - playwright: 1.39.0 + playwright: 1.42.1 dev: true /@popperjs/core@2.11.8: @@ -11478,18 +11478,18 @@ packages: find-up: 5.0.0 dev: true - /playwright-core@1.39.0: - resolution: {integrity: sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==} + /playwright-core@1.42.1: + resolution: {integrity: sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==} engines: {node: '>=16'} hasBin: true dev: true - /playwright@1.39.0: - resolution: {integrity: sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==} + /playwright@1.42.1: + resolution: {integrity: sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==} engines: {node: '>=16'} hasBin: true dependencies: - playwright-core: 1.39.0 + playwright-core: 1.42.1 optionalDependencies: fsevents: 2.3.2 dev: true