chore: add more e2e template settings tests (#12717)

This commit is contained in:
Kayla Washburn-Love 2024-03-28 16:00:27 -06:00 committed by GitHub
parent 8cf1e84bb5
commit f3cfe10c26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 87 additions and 41 deletions

View File

@ -472,11 +472,19 @@ jobs:
- run: pnpm playwright:install - run: pnpm playwright:install
working-directory: site 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: env:
DEBUG: pw:api DEBUG: pw:api
working-directory: site 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 - name: Upload Playwright Failed Tests
if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View File

@ -3,7 +3,7 @@ set -euo pipefail
workspace=${1:-} workspace=${1:-}
coder_repo=${2:-.} coder_repo=${2:-.}
port=${3:-3000} port=${3:-3111}
if [[ -z "${workspace}" ]]; then if [[ -z "${workspace}" ]]; then
echo "Usage: $0 <workspace> [workspace coder/coder dir] [e2e port]" echo "Usage: $0 <workspace> [workspace coder/coder dir] [e2e port]"

View File

@ -1,3 +1,7 @@
import * as path from "path";
export const coderMain = path.join(__dirname, "../../enterprise/cmd/coder");
// Default port from the server // Default port from the server
export const coderPort = process.env.CODER_E2E_PORT export const coderPort = process.env.CODER_E2E_PORT
? Number(process.env.CODER_E2E_PORT) ? Number(process.env.CODER_E2E_PORT)
@ -28,3 +32,5 @@ export const gitAuth = {
validatePath: "/validate", validatePath: "/validate",
installationsPath: "/installations", installationsPath: "/installations",
}; };
export const enterpriseLicense = process.env.CODER_E2E_ENTERPRISE_LICENSE ?? "";

View File

@ -6,6 +6,7 @@ import { storageState } from "./playwright.config";
test("setup first user", async ({ page }) => { test("setup first user", async ({ page }) => {
await page.goto("/", { waitUntil: "domcontentloaded" }); await page.goto("/", { waitUntil: "domcontentloaded" });
// Setup first user
await page.getByLabel(Language.usernameLabel).fill(constants.username); await page.getByLabel(Language.usernameLabel).fill(constants.username);
await page.getByLabel(Language.emailLabel).fill(constants.email); await page.getByLabel(Language.emailLabel).fill(constants.email);
await page.getByLabel(Language.passwordLabel).fill(constants.password); 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.context().storageState({ path: storageState });
await page.getByTestId("button-select-template").isVisible(); 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();
}
}); });

View File

@ -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 axios from "axios";
import { type ChildProcess, exec, spawn } from "child_process"; import { type ChildProcess, exec, spawn } from "child_process";
import { randomUUID } from "crypto"; import { randomUUID } from "crypto";
@ -12,7 +12,13 @@ import type {
UpdateTemplateMeta, UpdateTemplateMeta,
} from "api/typesGenerated"; } from "api/typesGenerated";
import { TarWriter } from "utils/tar"; import { TarWriter } from "utils/tar";
import { agentPProfPort, coderPort, prometheusPort } from "./constants"; import {
agentPProfPort,
coderMain,
coderPort,
enterpriseLicense,
prometheusPort,
} from "./constants";
import { import {
Agent, Agent,
type App, type App,
@ -25,6 +31,11 @@ import {
type RichParameter, type RichParameter,
} from "./provisionerGenerated"; } 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. // createWorkspace creates a workspace for a template.
// It does not wait for it to be running, but it does navigate to the page. // It does not wait for it to be running, but it does navigate to the page.
export const createWorkspace = async ( export const createWorkspace = async (
@ -147,7 +158,7 @@ export const sshIntoWorkspace = async (
binaryArgs: string[] = [], binaryArgs: string[] = [],
): Promise<ssh.Client> => { ): Promise<ssh.Client> => {
if (binaryPath === "go") { if (binaryPath === "go") {
binaryArgs = ["run", coderMainPath()]; binaryArgs = ["run", coderMain];
} }
const sessionToken = await findSessionToken(page); const sessionToken = await findSessionToken(page);
return new Promise<ssh.Client>((resolve, reject) => { return new Promise<ssh.Client>((resolve, reject) => {
@ -229,7 +240,7 @@ export const startAgent = async (
page: Page, page: Page,
token: string, token: string,
): Promise<ChildProcess> => { ): Promise<ChildProcess> => {
return startAgentWithCommand(page, token, "go", "run", coderMainPath()); return startAgentWithCommand(page, token, "go", "run", coderMain);
}; };
// downloadCoderVersion downloads the version provided into a temporary dir and // 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! // Allows users to more easily define properties they want for agents and resources!
type RecursivePartial<T> = { type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[] [P in keyof T]?: T[P] extends (infer U)[]
@ -686,7 +685,7 @@ export const updateTemplate = async (
"go", "go",
[ [
"run", "run",
coderMainPath(), coderMain,
"templates", "templates",
"push", "push",
"--test.provisioner", "--test.provisioner",

View File

@ -1,11 +1,9 @@
import { defineConfig } from "@playwright/test"; import { defineConfig } from "@playwright/test";
import path from "path"; import * as path from "path";
import { coderPort, coderdPProfPort, gitAuth } from "./constants"; import { coderMain, coderPort, coderdPProfPort, gitAuth } from "./constants";
export const wsEndpoint = process.env.CODER_E2E_WS_ENDPOINT; export const wsEndpoint = process.env.CODER_E2E_WS_ENDPOINT;
const coderMain = path.join(__dirname, "../../enterprise/cmd/coder");
// This is where auth cookies are stored! // This is where auth cookies are stored!
export const storageState = path.join(__dirname, ".auth.json"); export const storageState = path.join(__dirname, ".auth.json");
@ -16,16 +14,14 @@ const localURL = (port: number, path: string): string => {
export default defineConfig({ export default defineConfig({
projects: [ projects: [
{ {
name: "setup", name: "testsSetup",
testMatch: /global.setup\.ts/, testMatch: /global.setup\.ts/,
}, },
{ {
name: "tests", name: "tests",
testMatch: /.*\.spec\.ts/, testMatch: /.*\.spec\.ts/,
dependencies: ["setup"], dependencies: ["testsSetup"],
use: { use: { storageState },
storageState: storageState,
},
timeout: 60_000, timeout: 60_000,
}, },
], ],

View File

@ -1,5 +1,9 @@
import { test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import { createTemplate, updateTemplateSettings } from "../helpers"; import {
createTemplate,
requiresEnterpriseLicense,
updateTemplateSettings,
} from "../helpers";
test("template update with new name redirects on successful submit", async ({ test("template update with new name redirects on successful submit", async ({
page, page,
@ -10,3 +14,24 @@ test("template update with new name redirects on successful submit", async ({
name: "new-name", 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);
});

View File

@ -95,7 +95,7 @@
}, },
"devDependencies": { "devDependencies": {
"@octokit/types": "12.3.0", "@octokit/types": "12.3.0",
"@playwright/test": "1.39.0", "@playwright/test": "1.42.1",
"@storybook/addon-actions": "7.6.11", "@storybook/addon-actions": "7.6.11",
"@storybook/addon-essentials": "7.6.11", "@storybook/addon-essentials": "7.6.11",
"@storybook/addon-interactions": "7.6.11", "@storybook/addon-interactions": "7.6.11",

View File

@ -204,8 +204,8 @@ devDependencies:
specifier: 12.3.0 specifier: 12.3.0
version: 12.3.0 version: 12.3.0
'@playwright/test': '@playwright/test':
specifier: 1.39.0 specifier: 1.42.1
version: 1.39.0 version: 1.42.1
'@storybook/addon-actions': '@storybook/addon-actions':
specifier: 7.6.11 specifier: 7.6.11
version: 7.6.11 version: 7.6.11
@ -3064,12 +3064,12 @@ packages:
dev: true dev: true
optional: true optional: true
/@playwright/test@1.39.0: /@playwright/test@1.42.1:
resolution: {integrity: sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==} resolution: {integrity: sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
hasBin: true hasBin: true
dependencies: dependencies:
playwright: 1.39.0 playwright: 1.42.1
dev: true dev: true
/@popperjs/core@2.11.8: /@popperjs/core@2.11.8:
@ -11478,18 +11478,18 @@ packages:
find-up: 5.0.0 find-up: 5.0.0
dev: true dev: true
/playwright-core@1.39.0: /playwright-core@1.42.1:
resolution: {integrity: sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==} resolution: {integrity: sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==}
engines: {node: '>=16'} engines: {node: '>=16'}
hasBin: true hasBin: true
dev: true dev: true
/playwright@1.39.0: /playwright@1.42.1:
resolution: {integrity: sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==} resolution: {integrity: sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==}
engines: {node: '>=16'} engines: {node: '>=16'}
hasBin: true hasBin: true
dependencies: dependencies:
playwright-core: 1.39.0 playwright-core: 1.42.1
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2
dev: true dev: true