From 895bea7c527bfbb148a421b9283fa0805f6ab11d Mon Sep 17 00:00:00 2001 From: joshhunt Date: Mon, 30 Jun 2025 19:33:08 +0100 Subject: [PATCH] working dagger --- e2e-playwright/scenarios/login.spec.ts | 2 +- e2e-playwright/start-and-run-suite | 11 +- pkg/build/e2e-playwright/e2e.go | 146 +++++++++++++++++++++++++ pkg/build/e2e-playwright/main.go | 12 +- pkg/build/e2e-playwright/run-e2e.go | 79 ------------- playwright.config.ts | 5 +- 6 files changed, 167 insertions(+), 88 deletions(-) create mode 100644 pkg/build/e2e-playwright/e2e.go delete mode 100644 pkg/build/e2e-playwright/run-e2e.go diff --git a/e2e-playwright/scenarios/login.spec.ts b/e2e-playwright/scenarios/login.spec.ts index afef3d867d1..f212e28824c 100644 --- a/e2e-playwright/scenarios/login.spec.ts +++ b/e2e-playwright/scenarios/login.spec.ts @@ -1,7 +1,7 @@ import { test, expect } from '@grafana/plugin-e2e'; test( - 'Scenario test: Can login successfully', + 'Scenario test: Can actually login successfully', { tag: ['@scenarios'], }, diff --git a/e2e-playwright/start-and-run-suite b/e2e-playwright/start-and-run-suite index 3322f449345..4d47909c374 100755 --- a/e2e-playwright/start-and-run-suite +++ b/e2e-playwright/start-and-run-suite @@ -20,4 +20,13 @@ else ./scripts/grafana-server/wait-for-grafana fi -PORT=3001 HOST=localhost yarn playwright test +# Wait for health endpoint to be ready +echo "Waiting for Grafana health endpoint..." +while ! curl -s "http://localhost:3001/api/health" > /dev/null; do + sleep 1 +done +echo "Grafana health endpoint is ready" + + +# PORT=3001 HOST=localhost yarn playwright test +PORT=3001 HOST=localhost yarn playwright test --grep @panels diff --git a/pkg/build/e2e-playwright/e2e.go b/pkg/build/e2e-playwright/e2e.go new file mode 100644 index 00000000000..8eb3db64411 --- /dev/null +++ b/pkg/build/e2e-playwright/e2e.go @@ -0,0 +1,146 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "dagger.io/dagger" +) + +type Deps struct { + NodeVersion string + PlaywrightVersion string +} + +func GetVersions(ctx context.Context, src *dagger.Directory) (Deps, error) { + nvmrc, err := src.File(".nvmrc").Contents(ctx) + if err != nil { + return Deps{}, err + } + pkgJSON, err := src.File("package.json").Contents(ctx) + if err != nil { + return Deps{}, err + } + + // parse JSON in-Go, no jq needed + var pkg struct { + DevDependencies map[string]string `json:"devDependencies"` + } + if err := json.Unmarshal([]byte(pkgJSON), &pkg); err != nil { + return Deps{}, err + } + + return Deps{ + NodeVersion: strings.TrimSpace(strings.TrimPrefix(nvmrc, "v")), + PlaywrightVersion: strings.TrimSpace(pkg.DevDependencies["@playwright/test"]), + }, nil +} + +func RunTest( + ctx context.Context, + d *dagger.Client, + grafanaService *dagger.Service, +) (*dagger.Container, error) { + + grafanaDir := "." // TODO: arg + + nxCache := d.CacheVolume("nx-cache") + + // Minimal files needed to run yarn install + yarnHostSrc := d.Host().Directory(grafanaDir, dagger.HostDirectoryOpts{ + Include: []string{ + "package.json", + "yarn.lock", + ".yarnrc.yml", + ".yarn", + "packages/*/package.json", + "public/app/plugins/*/*/package.json", + "e2e/test-plugins/*/package.json", + ".nvmrc", + }, + }) + + // Files needed to run e2e tests. Above files will be copied into the test runner container as well. + e2eHostSrc := d.Host().Directory(".", dagger.HostDirectoryOpts{ + Include: []string{ + "public/app/types/*.d.ts", + "public/app/core/icons/cached.json", + + // packages we use in playwright tests + "packages", // TODO: do we need all of this? + "e2e/test-plugins", + "public/app/plugins", // TODO: do we need all of this? + + // e2e files + "e2e-playwright", + "playwright.config.ts", + }, + Exclude: []string{ + "**/dist", + }, + }) + + deps, err := GetVersions(ctx, yarnHostSrc) + if err != nil { + return nil, err + } + + nodeBase := WithNode(d, deps.NodeVersion) + playwrightBase := WithPlaywright(d, nodeBase, deps.PlaywrightVersion) + + e2eContainer := WithYarnInstall(d, playwrightBase, yarnHostSrc). + WithWorkdir("/src"). + WithDirectory("/src", e2eHostSrc). + WithMountedCache(".nx", nxCache). + WithExec([]string{"yarn", "e2e:plugin:build"}). + WithEnvVariable("HOST", grafanaHost). + WithEnvVariable("PORT", fmt.Sprint(grafanaPort)). + WithServiceBinding(grafanaHost, grafanaService). + WithEnvVariable("PLAYWRIGHT_HTML_OPEN", "never"). + WithExec([]string{"yarn", "e2e:playwright"}, dagger.ContainerWithExecOpts{ + Expect: dagger.ReturnTypeAny, + }) + + // TODO: wrap in conditional arg + _, err = e2eContainer.Directory("/src/playwright-report").Export(ctx, "./dist/playwright-report") + if err != nil { + return nil, err + } + + _, err = e2eContainer.Directory("/src/test-results").Export(ctx, "./dist/test-results") + if err != nil { + return nil, err + } + + return e2eContainer, nil +} + +func WithNode(d *dagger.Client, version string) *dagger.Container { + nodeImage := fmt.Sprintf("node:%s-slim", strings.TrimPrefix(version, "v")) + return d.Container().From(nodeImage) +} + +func WithPlaywright(d *dagger.Client, base *dagger.Container, version string) *dagger.Container { + brCache := d.CacheVolume("playwright-browsers") + return base. + WithEnvVariable("PLAYWRIGHT_BROWSERS_PATH", "/playwright-cache"). + WithMountedCache("/playwright-cache", brCache). + WithExec([]string{"npx", "-y", "playwright@" + version, "install", "--with-deps"}) +} + +func WithYarnInstall(d *dagger.Client, base *dagger.Container, yarnHostSrc *dagger.Directory) *dagger.Container { + yarnCache := d.CacheVolume("yarn-cache") + + return base. + WithWorkdir("/src"). + WithMountedCache("/.yarn", yarnCache). + WithEnvVariable("YARN_CACHE_FOLDER", "/.yarn"). + + // It's important to copy all files here because the whole src directory is then copied into the test runner container + WithDirectory("/src", yarnHostSrc). + WithExec([]string{"corepack", "enable"}). + WithExec([]string{"corepack", "install"}). + WithExec([]string{"yarn", "install", "--immutable"}) +} diff --git a/pkg/build/e2e-playwright/main.go b/pkg/build/e2e-playwright/main.go index 2f5855f1f82..4e0d860d559 100644 --- a/pkg/build/e2e-playwright/main.go +++ b/pkg/build/e2e-playwright/main.go @@ -118,26 +118,26 @@ func run(ctx context.Context, cmd *cli.Command) error { c, runErr := RunTest(ctx, d, svc) if runErr != nil { - return fmt.Errorf("failed to run a11y test suite: %w", runErr) + return fmt.Errorf("failed to run e2e test suite: %w", runErr) } c, syncErr := c.Sync(ctx) if syncErr != nil { - return fmt.Errorf("failed to sync a11y test suite: %w", syncErr) + return fmt.Errorf("failed to sync e2e test suite: %w", syncErr) } code, codeErr := c.ExitCode(ctx) if codeErr != nil { - return fmt.Errorf("failed to get exit code of a11y test suite: %w", codeErr) + return fmt.Errorf("failed to get exit code of e2e test suite: %w", codeErr) } if code == 0 { - log.Printf("a11y tests passed with exit code %d", code) + log.Printf("e2e tests passed with exit code %d", code) } else { - return fmt.Errorf("a11y tests failed with exit code %d", code) + return fmt.Errorf("e2e tests failed with exit code %d", code) } - log.Println("a11y tests completed successfully") + log.Println("e2e tests completed successfully") return nil } diff --git a/pkg/build/e2e-playwright/run-e2e.go b/pkg/build/e2e-playwright/run-e2e.go deleted file mode 100644 index 883a6eced2e..00000000000 --- a/pkg/build/e2e-playwright/run-e2e.go +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - "context" - "fmt" - "strings" - - "dagger.io/dagger" -) - -// NodeVersionContainer returns a container whose `stdout` will return the node version from the '.nvmrc' file in the directory 'src'. -func NodeVersion(d *dagger.Client, src *dagger.File) *dagger.Container { - return d.Container().From("alpine:3"). - WithMountedFile("/src/.nvmrc", src). - WithWorkdir("/src"). - WithExec([]string{"cat", ".nvmrc"}) -} - -func NodeImage(version string) string { - return fmt.Sprintf("node:%s-slim", strings.TrimPrefix(strings.TrimSpace(version), "v")) -} - -func RunTest( - ctx context.Context, - d *dagger.Client, - grafanaService *dagger.Service, -) (*dagger.Container, error) { - - // Explicitly only the files u'sed by e2e tests - hostSrc := d.Host().Directory(".", dagger.HostDirectoryOpts{ - Include: []string{ - // Include all files for a valid yarn workspace install - "package.json", - "yarn.lock", - ".yarnrc.yml", - ".yarn", - "packages/*/package.json", - "public/app/plugins/*/*/package.json", - "e2e/test-plugins/*/package.json", - ".nvmrc", - "public/app/types/*.d.ts", - - // packages we use in playwright tests - "packages", - - // e2e files - "e2e-playwright", - }, - Exclude: []string{ - "packages/*/dist", - }, - }) - - nodeVersion, err := NodeVersion(d, hostSrc.File(".nvmrc")).Stdout(ctx) - if err != nil { - return nil, err - } - - yarnCache := d.CacheVolume("yarn-cache") - yarnCacheDir := "/yarn-cache" - - pa11yContainer := d.Container().From(NodeImage(nodeVersion)). - WithExec([]string{"npx", "-y", "playwright@1.52.0", "install", "--with-deps"}). // TODO: sync version from package.json - WithWorkdir("/src"). - WithDirectory("/src", hostSrc). - WithMountedCache(yarnCacheDir, yarnCache). - WithExec([]string{"corepack", "enable"}). - WithExec([]string{"corepack", "install"}). - WithEnvVariable("YARN_CACHE_FOLDER", yarnCacheDir). - WithExec([]string{"yarn", "config", "get", "cacheFolder"}). - WithExec([]string{"yarn", "install", "--immutable"}). - // WithExec([]string{"yarn", "e2e:plugin:build"}). - WithEnvVariable("HOST", grafanaHost). - WithEnvVariable("PORT", fmt.Sprint(grafanaPort)). - WithExec([]string{"yarn", "packages:build"}). - WithExec([]string{"yarn", "e2e:playwright"}) - - return pa11yContainer, nil -} diff --git a/playwright.config.ts b/playwright.config.ts index 7f3d82765f8..3e1860024ee 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -12,7 +12,10 @@ export default defineConfig({ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, - reporter: 'html', + reporter: [ + ['list'], // for terminal + ['html'], // pretty + ], use: { baseURL: `http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}`, trace: 'retain-on-failure',