diff --git a/.agents/workflows/pmmLogin.md b/.agents/workflows/pmmLogin.md index 8965bf6a5..0bde93617 100644 --- a/.agents/workflows/pmmLogin.md +++ b/.agents/workflows/pmmLogin.md @@ -1,28 +1,47 @@ ---- -description: PMM Login using basic Auth headers +--- +description: PMM Login --- -- NEVER use UI login form. -- Use Basic Auth header via `mcp_playwright_browser_run_code`. -- DO NOT pass plain credentials in the URL string. +Use `GrafanaHelper.authorize` flow, then open Help. Tour completed; updates unavailable. ```javascript async (page) => { const base = "https://127.0.0.1"; - const auth = Buffer.from("admin:admin").toString("base64"); + const user = "admin"; + const password = "admin"; + const auth = "YWRtaW46YWRtaW4="; await page.context().setExtraHTTPHeaders({ Authorization: `Basic ${auth}` }); - await page.route("**/api/user/auth-tokens/rotate", async (route) => { - await route.fulfill({ - body: "{}", + await page.route("**/v1/users/me", (route) => + route.fulfill({ + body: JSON.stringify({ + alerting_tour_completed: true, + product_tour_completed: true, + snoozed_pmm_version: "", + user_id: 1, + }), + contentType: "application/json", + status: 200, + }), + ); + + await page.route("**/v1/server/updates?force=**", (route) => + route.fulfill({ + body: JSON.stringify({ + installed: {}, + last_check: new Date().toISOString(), + latest: {}, + update_available: false, + }), contentType: "application/json", status: 200, - }); - }); + }), + ); - await page.goto(`${base}/pmm-ui/help`); -}; + await page.request.post(`${base}/graph/login`, { data: { user, password } }); + await page.goto(`${base}/pmm-ui/help`, { waitUntil: "domcontentloaded" }); +} ``` -- Reply `Done` immediately after logging in. NO extra info. +Reply `Done`. diff --git a/.cursor/Dockerfile b/.cursor/Dockerfile new file mode 100644 index 000000000..9e0df9c8b --- /dev/null +++ b/.cursor/Dockerfile @@ -0,0 +1,52 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y \ + bash \ + ca-certificates \ + curl \ + git \ + gnupg \ + python3 \ + python3-pip \ + python3-venv \ + sudo \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y nodejs \ + && rm -rf /var/lib/apt/lists/* + +######################################################## +# Docker (Cursor cloud agent — docker-in-docker) +# https://cursor.com/docs/cloud-agent/setup#running-docker +######################################################## + +RUN install -m 0755 -d /etc/apt/keyrings \ + && curl --retry 3 --retry-delay 5 -fsSL https://download.docker.com/linux/ubuntu/gpg \ + | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ + && chmod a+r /etc/apt/keyrings/docker.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + | tee /etc/apt/sources.list.d/docker.list > /dev/null \ + && apt-get update \ + && apt-get install -y \ + docker-ce \ + docker-ce-cli \ + containerd.io \ + docker-buildx-plugin \ + docker-compose-plugin \ + && rm -rf /var/lib/apt/lists/* + +RUN apt-get update && apt-get install -y fuse-overlayfs iptables && rm -rf /var/lib/apt/lists/* \ + && mkdir -p /etc/docker \ + && printf '%s\n' '{' ' "storage-driver": "fuse-overlayfs"' '}' > /etc/docker/daemon.json \ + && update-alternatives --set iptables /usr/sbin/iptables-legacy \ + && update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy + +RUN id -u ubuntu &>/dev/null || useradd -m -s /bin/bash ubuntu \ + && groupadd -f docker \ + && usermod -aG docker ubuntu \ + && usermod -aG sudo ubuntu \ + && echo "ubuntu ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ubuntu diff --git a/.cursor/environment.json b/.cursor/environment.json new file mode 100644 index 000000000..9dfaafee4 --- /dev/null +++ b/.cursor/environment.json @@ -0,0 +1,8 @@ +{ + "build": { + "context": "..", + "dockerfile": ".cursor/Dockerfile" + }, + "install": "cd e2e_tests && npm ci && npx playwright install-deps && npx playwright install chromium", + "start": "sudo service docker start || true" +} diff --git a/.cursor/scripts/run-migration-single-test.sh b/.cursor/scripts/run-migration-single-test.sh new file mode 100755 index 000000000..6e313e14b --- /dev/null +++ b/.cursor/scripts/run-migration-single-test.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Canonical: .cursor/scripts/run-migration-single-test.sh +# Provision minimal PMM env for one Playwright test, then run only that file. +# Usage (from repo root): +# ./.cursor/scripts/run-migration-single-test.sh tests/leftNavigation.test.ts +# ./.cursor/scripts/run-migration-single-test.sh tests/configuration/pmmInventory.test.ts '--database pdpgsql' false + +set -euo pipefail + +TEST_FILE="${1:?usage: run-migration-single-test.sh [setup_services] [setup_client]}" +SETUP_SERVICES="${2:-}" +SETUP_CLIENT="${3:-false}" # true only for standalone PMM Client/node setup outside pmm-framework provisioning +ADMIN_PASSWORD="${ADMIN_PASSWORD:-admin-password}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +DOCKER_VERSION="perconalab/pmm-server:3-dev-latest" + +bash "${SCRIPT_DIR}/start-docker-microvm.sh" + +docker network create pmm-qa || true +docker volume create pmm-volume || true + +cd "$REPO_ROOT/e2e_tests" +export ADMIN_PASSWORD +export DOCKER_VERSION +docker compose -f docker-compose.yml up -d + +echo "Waiting for PMM readyz..." +bash "${SCRIPT_DIR}/wait-pmm-ready.sh" "http://127.0.0.1/v1/server/readyz" + +if [[ "$SETUP_CLIENT" == "true" ]]; then + cd "$REPO_ROOT/qa-integration/pmm_qa" + sudo bash -x pmm3-client-setup.sh \ + --pmm_server_ip 127.0.0.1 \ + --client_version "${PMM_CLIENT_VERSION:-latest-tarball}" \ + --admin_password "$ADMIN_PASSWORD" \ + --use_metrics_mode no +fi + +if [[ -n "$SETUP_SERVICES" ]]; then + cd "$REPO_ROOT/qa-integration/pmm_qa" + python3 -m venv virtenv + # shellcheck disable=SC1091 + source virtenv/bin/activate + pip install --upgrade pip + pip install -r requirements.txt setuptools + # shellcheck disable=SC2086 + python pmm-framework.py --verbosity-level=2 --pmm-server-password="$ADMIN_PASSWORD" $SETUP_SERVICES +fi + +cd "$REPO_ROOT/e2e_tests" +export PMM_UI_URL=http://127.0.0.1/ +export WORKERS=1 +export HEADLESS=true + +npx playwright test "$TEST_FILE" diff --git a/.cursor/scripts/start-docker-microvm.sh b/.cursor/scripts/start-docker-microvm.sh new file mode 100644 index 000000000..560b6f9b1 --- /dev/null +++ b/.cursor/scripts/start-docker-microvm.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Start Docker in Cursor MicroVM environments where systemd may be unavailable. +set -euo pipefail + +DOCKERD_SESSION="dockerd" +TMUX_CONF="/exec-daemon/tmux.portal.conf" + +if ! command -v dockerd >/dev/null 2>&1; then + echo "ERROR: dockerd not installed. Install docker-ce first." >&2 + exit 1 +fi + +if docker info >/dev/null 2>&1; then + echo "Docker is ready ($(docker info -f '{{.ServerVersion}}'), storage: $(docker info -f '{{.Driver}}'))" + exit 0 +fi + +sudo service docker start >/dev/null 2>&1 || true +if docker info >/dev/null 2>&1; then + echo "Docker is ready ($(docker info -f '{{.ServerVersion}}'), storage: $(docker info -f '{{.Driver}}'))" + exit 0 +fi + +if command -v tmux >/dev/null 2>&1 && [ -f "$TMUX_CONF" ]; then + TMUX=(tmux -f "$TMUX_CONF") +else + TMUX=(tmux) +fi + +if command -v tmux >/dev/null 2>&1; then + if ! "${TMUX[@]}" has-session -t "=$DOCKERD_SESSION" 2>/dev/null; then + echo "Starting dockerd in tmux session '$DOCKERD_SESSION'..." + "${TMUX[@]}" new-session -d -s "$DOCKERD_SESSION" -c /tmp -- "sudo dockerd" + fi +else + echo "Starting dockerd in background..." + sudo dockerd >/tmp/dockerd.log 2>&1 & +fi + +for _ in $(seq 1 30); do + if [ -S /var/run/docker.sock ]; then + break + fi + sleep 1 +done + +if [ ! -S /var/run/docker.sock ]; then + echo "ERROR: dockerd did not create /var/run/docker.sock within 30s." >&2 + exit 1 +fi + +if ! docker info >/dev/null 2>&1; then + sudo chmod 666 /var/run/docker.sock || true +fi + +if ! docker info >/dev/null 2>&1; then + echo "ERROR: cannot talk to Docker API after starting dockerd." >&2 + exit 1 +fi + +echo "Docker is ready ($(docker info -f '{{.ServerVersion}}'), storage: $(docker info -f '{{.Driver}}'))" \ No newline at end of file diff --git a/.cursor/scripts/wait-pmm-ready.sh b/.cursor/scripts/wait-pmm-ready.sh new file mode 100644 index 000000000..81936be95 --- /dev/null +++ b/.cursor/scripts/wait-pmm-ready.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Wait until PMM Server readyz returns HTTP 200. If the body is present, it must be either "{}" or contain "OK". +set -euo pipefail + +READYZ_URL="${1:-${PMM_READY_URL:-http://127.0.0.1/v1/server/readyz}}" +ADMIN_PASSWORD="${ADMIN_PASSWORD:-admin-password}" +TIMEOUT_SEC="${PMM_READY_TIMEOUT:-300}" +INTERVAL_SEC="${PMM_READY_INTERVAL:-5}" +BODY_FILE="${TMPDIR:-/tmp}/pmm-readyz-body.txt" + +elapsed=0 +while [ "$elapsed" -lt "$TIMEOUT_SEC" ]; do + http_code=$(curl -ksS -o "$BODY_FILE" -w '%{http_code}' --user "admin:${ADMIN_PASSWORD}" "$READYZ_URL" 2>/dev/null || echo "000") + body=$(tr -d '[:space:]' <"$BODY_FILE" 2>/dev/null || true) + + if [ "$http_code" = "200" ] && { [ -z "$body" ] || [ "$body" = "{}" ] || [ "$body" = "OK" ]; }; then + echo "PMM Server is ready (${READYZ_URL} -> HTTP 200, body=${body:-empty})" + exit 0 + fi + + preview=$(head -c 120 "$BODY_FILE" 2>/dev/null | tr '\n' ' ' || true) + echo "waiting for readyz... HTTP ${http_code} (${elapsed}s/${TIMEOUT_SEC}s) ${preview}" + sleep "$INTERVAL_SEC" + elapsed=$((elapsed + INTERVAL_SEC)) +done + +echo "ERROR: PMM Server not ready after ${TIMEOUT_SEC}s (${READYZ_URL})" >&2 +exit 1 \ No newline at end of file diff --git a/.cursor/skills/codeceptjs-migration/SKILL.md b/.cursor/skills/codeceptjs-migration/SKILL.md new file mode 100644 index 000000000..a55624019 --- /dev/null +++ b/.cursor/skills/codeceptjs-migration/SKILL.md @@ -0,0 +1,120 @@ +--- +name: codeceptjs-migration +description: AIOnly CodeceptJS->Playwright migration+post-migration audit. +--- + +# CodeceptJS->PlaywrightAI + +MODE:AIOnly;Output<=8Lines!UserRequestsDetails(DoDReportExemptFrom8LineLimit). +REF:ActualCodeceptSourceUnder`codeceptjs-e2e/`=SourceOfTruth;MandatoryPostMigrationSideBySideAudit. + +## Scope + +Folders:`tests//*_test.js`->`e2e_tests/tests//*.test.ts`. +POMs:`tests/pages/`->`e2e_tests/pages//*.page.ts`. +APIs:`tests/pages/api/`->`e2e_tests/api/*.api.ts`. +Aliases:`@fixtures/*`,`@helpers/*`,`@pages/*`,`@api/*`,`@components/*`,`@interfaces/*`. +LoadThis:`references/mappings.md`+`references/examples-test.md`+`references/examples-pom.md`;ReadTheActualCodeceptSourceTest+ItsPOMs/CustomStepsWhenMigratingOrAuditing. + +## Rules + +OmitSkippedOnly?SkippedANDCommentedOut. +MigratedTestsNoComments. +Hooks:`Before`->`pmmTest.beforeEach`;`After`->`pmmTest.afterEach`. +DataDriven->TSArray+`for...of`;InjectLoopVarsIntoTitleString. +Tags->PreserveExactInTitle:`pmmTest('Title @tag',...)`. +Retries->DoNotMigrate`.retry(N)`;CIHandles. +MirrorOriginalLogic/Flow/AssertionsStrictly;DoNotInventCoverage. +BestFitTarget:BeforeCreatingAnyNewPlaywrightTestFile,ReadSourceBehavior+`context.md`§4Inventory;PlaceScenariosInExisting`e2e_tests`FileWhenPage/Feature/Hook/FixtureMatch(e.g.helpPageScenarios→`helpCenter.test.ts`,Nav→`navigation.test.ts`).CreateNew`*.test.ts`OnlyWhenNoSuitableFileExists;RecordActualTargetInTracker. +SourceRenameOnDone:AfterLiveRunPASS,`git mv``codeceptjs-e2e/tests//_test.js`→`_migrated.js`(SameDir).CodeceptCIUses`tests/**/*_test.js`(`pr.codecept.js`);`_migrated.js`ExcludedFromWorkflows.KeepOriginalAsReference. +InstructionsBranch=`PMM-7-codeceptjs-migration`(ReadTracker+Skills;PushTracker+DocsHere).PRBase=`main`.PRScope=`e2e_tests/**`+CodeceptRenameOnly;No`.cursor/**`InTestPRs.BranchMigratePRsFrom`main`NotPMM-7. + +## File Mutation Rules + +PreferEditOverWrite:NEVERUse`Write`OnExistingPOM,Helper,OrTestFileToAddFunctionality.Use`Edit`ToSurgicallyInsertNewMembers,Methods,OrProperties. +PreservationAudit:BeforeCompletingAFileUpdate,VerifyThatStandaloneProperties(Constants,ErrorMessages,OrConfigStrings)ThatWereInTheOriginalFileAreStillPresent. +NoAccidentalSimplification:EnsureExistingMethodsAreNotReplacedByASimplerVersionUnlessTheMigrationExplicitlyRequiresALogicChange. +DiffVerification:AfterAnyMultiLine`Edit`,VerifyTheSurroundingContextToEnsureTheFileStructure(Brackets,Imports,ClassDefinitions)RemainsIntact. +ReadImmediateEdit:AlwaysReadTargetBlockImmediatelyBeforeEditToAvoidNewline/IndentationMismatches. +UseAnchorStrings:TargetUniqueSingleLines(e.g.PropertyStart)ToMinimizeMatchFailures. +SafeMergeFallback:IfEditFailsTwice->ReadEntireFile->MergeLogic->WriteEntireFile->ReadVerify. + +## Custom Step Resolution + +NoInlining:DoNotInlineLogicOf`custom_steps.js`IntoTestFiles. +Mapping:MapCustomStepsToTheirNewEquivalentsIn`@helpers`Or`@components`. +Discovery:IfCustomStepNotIn`mappings.md`,Read`codeceptjs-e2e/tests/custom_steps.js`ToDetermineLogicBeforeMigrating. + +## Polling & Wait Logic + +DeleteManualLoops:ReplaceManualWhileLoops(e.g.`asyncWaitFor`,`verifyInvisible`)WithPlaywrightNativeAssertions. +`verifyInvisible`->`expect(locator).toBeHidden({timeout})`. +`asyncWaitFor`->`expect.poll(async()->{...},{timeout})`. + +## POM/Locators + +URLsInPOMOnly. +LocatorsUse:`getByTestId`,`getByRole`,`locator`;`$foo`->`getByTestId('foo')`. +`locate().find()`->Chained`.locator()`. +UseExisting`e2e_tests`POMShape:`urls`,`elements`,`buttons`,`inputs`,etc. +RegisterNewPOMFixturesIn`pmmTest.ts`. +POMsExtend`BasePage`. +BrokenLocatorFix:TraceOnFailureFirst(`npx playwright show-trace`);MCPFallbackOnlyIfTraceInsufficient(SharedDocsUnder`.agents/workflows/`:pmmLogin+mcpRules);PreserveSameElementSemantics. + +## API/Waits + +APIPaths->`e2e_tests/helpers/apiEndpoints.ts`;APIClasses->`e2e_tests/api/*.api.ts`;RegisterIn`e2e_tests/api/api.ts`. +PreferAutoWait. Explicit`I.wait()`Only->TimeoutsEnum(e.g.`Timeouts.TEN_SECONDS`). + +## Interactions/Assertions + +Input->`locator.clear()`Then`.fill()`;Never`.evaluate()`ToClear. +`I.see`->`toContainText`;`I.seeElement`->`toBeVisible`. + +## Helpers + +HelperEligibility:OnlyMoveLogicInto`@helpers`WhenItIsUsedInMoreThanOneTestFile.AssertionWrappersOrUtilitiesUsedInAnASingleTestMustStayInlineinThatTest;DoNotCreateAHelperJustToOrganiseOneTest. +NoExpectsInHelpers:`Expect()`CallsMustAlwaysBeVisibleInTheTestBody.NeverWrapExpectInsideAHelperFunctionToSilence`playwright/expect-expect`.InThatRuleFires,TheDesignIsWrong-RestructureTheCodeNotTheRule.AnEslintDisableForExpectExpectIsARedFlag;RefactorInstead. + +## Lint + +ArrowFunctionsOnly;NoTraditional`function`. +NumericSeparators:`30_000`. +FileNamesCamelCase. +No`.skip()`;NoCommentedTests. +ESLintDisableCommentsRequireReason;UseOnlyWhenNeeded. +NeverUseEslintDisableFor`playwright/expect-expect`;IfTriggered,BringExpectsInlineIntoTest. + +## Workflow + +SkepticalPipeline: + +1. Analysis:ReadSourceTest->IdentifyAllCalls->ListCustomSteps. +2. Mapping:Use`references/mappings.md`+`references/examples-test.md`ToMapEveryCallToPlaywrightEquivalent. +3. Implementation:GenerateCodeUsing`references/examples-pom.md`AsArchitecturalGuide. +4. SkepticalAudit:ActAsSkepticalQA->SearchFor3PotentialBugs/LogicLoss->RefuteFindings. +5. FinalDoD:OutputChecklist->Verdict->Report. + +## CriticalAuditGate + +BehaviorPreservationIsNonNegotiable:MigratedTestMUSTReproduceSourceExactly->SameFlow,Setup/Cleanup(Before/After),EveryAssertion+ItsSemantics,DataDrivenIterations,Tags,LocatorTargets.NoAdded/Removed/Weakened/"Improved"Coverage.NoInventedWaits/Shortcuts.FaithfulMappingImpossible->Stop+ReportInsteadOfApproximate. +BeforeFinalCompareOriginalCodeceptTestVsMigratedPlaywrightLineByLineForBehavior.NotEnoughContext->ReadMoreTargetedFiles. +PASSOnlyWhenNoUnexplainedLogicLoss,NoMissingAssertions,NoChangedSetup/Cleanup,NoLocatorSemanticsDrift. +ConfidenceGate:EmitExplicitConfidence%.DoNotExecute/LiveRunUntilConfidence>95%WithZeroUnexplainedDiscrepancies.<=95%->DoNotRun;IterateMigrationOrReportGaps+Stop. + +## Definition of Done (DoD) + +AMigrationIsNOTCompleteUntilTheFollowingSequenceIsExplicitlyOutputted: + +1. **Audit Checklist**: ALineByLineChecklistMatchingThe`CriticalAuditGate`Criteria. +2. **Verdict**: AClear`PASS`Or`FAIL`BasedOnTheChecklist. +3. **Final Report**: TheStructuredSummaryContaining: + - Files Changed + - Validation Run (e.g., "TS Compile: Pass", "Lint: Pass") + - Discrepancies (Must be "None" for a PASS) + - Confidence % (Must be >95% before any live run; otherwise Verdict=FAIL/needs-review, do not execute) + FailureToProvideThisExactSequenceIsAViolationOfTheSkill'sOperationalProtocol. + +## Search + +TargetedReadsOnly;AvoidRepoWideSearchUnlessTargetUnknown. diff --git a/.cursor/skills/codeceptjs-migration/context.md b/.cursor/skills/codeceptjs-migration/context.md new file mode 100644 index 000000000..d3ae72311 --- /dev/null +++ b/.cursor/skills/codeceptjs-migration/context.md @@ -0,0 +1,202 @@ +# Migration Context Pack + +Read this FIRST on every daily run. It exists so the run does not re-scan the whole repo +(~73 CodeceptJS POM/API/custom-step files + ~93 `e2e_tests` files). Per-run reads should be +bounded to: this file + `tracker.md` + the one source test + its specific POMs/API + the +target registration files listed under "Registration points". + +Regenerate the inventory / mapping sections when repo structure or the CI matrix changes. + +## 1. Provisioning (how server + client instances are set up) + +The canonical provisioning path for migration live runs is `.cursor/scripts/run-migration-single-test.sh` from repo root. The script starts Docker, creates the `pmm-qa` network and `pmm-volume`, starts PMM Server with `e2e_tests/docker-compose.yml` and fixed image `perconalab/pmm-server:3-dev-latest`, optionally attaches a standalone PMM Client, optionally runs `pmm-framework.py`, and then runs exactly one Playwright file. + +The script arguments are: + +```bash +./.cursor/scripts/run-migration-single-test.sh '' '' +``` + +- `` is relative to `e2e_tests/`. +- `` is the tracker `Setup` value, or an empty string when no DB/service provisioning is needed. +- `` is `true` only for a standalone PMM Client/node outside `pmm-framework.py` provisioning. Use `false` for pure UI tests and for DB/service tests where `pmm-framework.py` creates monitored services. + +Server image is fixed to `perconalab/pmm-server:3-dev-latest`. Default password is `ADMIN_PASSWORD=admin-password`; the script uses it consistently for PMM readiness, PMM Client setup, `pmm-framework.py`, and Playwright. Override `ADMIN_PASSWORD=...` only when intentionally testing a different password. PMM UI base URL is `http://127.0.0.1/`. + +The basis for `setup_services` is the test's tag mapped to the CI setup matrix in `.github/workflows/e2e-tests-matrix.yml` and `.github/workflows/runner-e2e-tests-codeceptjs.yml`. Where a test's tag is not in the matrix, derive the flags by reading the source test and matching a setup key from `qa-integration/README.md`. + +`pmm-framework.py` REQUIRES an already-running PMM server (the script provides this). Args include `--database` (repeatable), `--pmm-server-ip`, `--pmm-server-password`, `--client-version`, `--verbose`, and `--client-debug`. + +### 1d. Tag -> setup_services mapping (from e2e-tests-matrix.yml) + +| Tag(s) | setup_services | +| --- | --- | +| `@settings`, `@cli` | `--database pgsql` | +| `@ssl-mysql` | `--database ssl_mysql` | +| `@ssl-mongo` | `--database ssl_psmdb` | +| `@ssl-postgres` | `--database ssl_pdpgsql=16` | +| `@inventory`, `@image-renderer` | `--database pdpgsql` | +| `@LBAC` | `--database ps=8.4 --database psmdb --database pdpgsql` | +| `@new-navigation` | `--database ps=8.4 --database psmdb --database valkey` | +| `@pmm-ps-pxc-haproxy-integration` | `--database haproxy --database ps --database pxc` | +| `@pmm-valkey-integration` | `--database valkey` | +| `@disconnect` | none (no DB) | + +For tests with no DB dependency (pure UI/navigation/help/tour), pass an empty `setup_services` string. Use `setup_client=true` only when the source test needs a standalone client/node. + +### 1e. Setup key catalog (from qa-integration/README.md + scripts/database_options.py) + +`--database [=version][,PARAM=value,...]`. Keys and default versions (last in list is the +framework default): + +- `mysql`: 5.7, 8.0, 8.4, 9.7 (SETUP_TYPE: replication|gr; QUERY_SOURCE: perfschema|slowlog) +- `ps`: 5.7, 8.4, 8.0 (SETUP_TYPE: replication|gr; MY_ROCKS, BACKUP, NODES_COUNT) +- `pxc`: 5.7, 8.0 (fixed 3-node PXC + ProxySQL) +- `pgsql`: 11..18 (QUERY_SOURCE pgstatements; SETUP_TYPE: replication) +- `pdpgsql`: 11..18 (SETUP_TYPE: replication|patroni) +- `psmdb`: 4.4, 5.0, 6.0, 7.0, 8.0, latest (SETUP_TYPE: pss|psa|shards) +- `valkey`: 7, 8 (SETUP_TYPE: sentinel) +- `proxysql`: 2 ; `haproxy`: default +- `external`: redis_exporter (1.14.0/1.58.0), node_process_exporter (0.7.5/0.7.10) +- Variants: `ssl_mysql`, `ssl_pdpgsql`, `ssl_psmdb`, `mlaunch_psmdb`, `mlaunch_modb`, `ssl_mlaunch`, `dockerclients`, `bucket` + +### 1f. Branch strategy (instructions vs merge) + +| Branch | Role | What commits land here | +| --- | --- | --- | +| `PMM-7-codeceptjs-migration` | Instructions & tracking | `.cursor/skills/codeceptjs-migration/**`, `.cursor/scripts/**`, tracker row updates | +| `main` | Production | `e2e_tests/**`, Codecept `*_migrated.js` renames, new helpers/API/POMs | + +**Every daily run:** + +1. `git fetch origin main PMM-7-codeceptjs-migration` +2. Read tracker + skills from `PMM-7-codeceptjs-migration` (instructions source of truth). +3. Branch from **`main`** for test code (`migrate--`). Do **not** branch migrate PRs from PMM-7. +4. Open per-test PRs with **base `main`** (`gh pr create --base main ...`). PR scope: `e2e_tests/**` + Codecept renames only — **no** `.cursor/**`. +5. Push tracker/docs updates to **`PMM-7-codeceptjs-migration`** separately (second commit/push on that branch). + +Never use PMM-7 as the PR base for migrated test code. + +## 2. Repo map (source -> target) + +| CodeceptJS source | Playwright target | +| --- | --- | +| `codeceptjs-e2e/tests//*_test.js` | best-fit existing `e2e_tests/tests/**/*.test.ts` (see §2a) | +| `codeceptjs-e2e/tests//*_migrated.js` | already migrated; reference only — not run by Codecept CI | +| `codeceptjs-e2e/tests/**/pages/*.js` (POMs) | `e2e_tests/pages//*.page.ts` | +| `codeceptjs-e2e/tests/**/pages/api/*.js` (API) | `e2e_tests/api/*.api.ts` | +| `codeceptjs-e2e/tests/custom_steps.js` | `@helpers/*` or `@components/*` (see section 5) | +| `codeceptjs-e2e/testdata/` | `e2e_tests/testdata/` | + +### 2a. Best-fit placement (prefer existing Playwright files) + +Before creating a new `e2e_tests/tests/**/*.test.ts`, match on **behavior**, not the Codecept filename: + +| Source behavior | Typical best-fit target | +| --- | --- | +| Help page (`pmm-ui/help`), export logs, docs/forum links | `tests/helpCenter.test.ts` | +| Left menu collapse, menu traversal, time-range persistence | `tests/navigation.test.ts` | +| QAN RTA flows | `tests/qan/rta/*.test.ts` | +| Inventory services/agents | `tests/inventory/*.test.ts` | +| Valkey dashboards | `tests/dashboards/valkey/valkeyDashboards.test.ts` | + +Append migrated scenarios to the best-fit file when hooks/fixtures align. Create a new test file only when no suitable target exists. Update the tracker `Target` column to the actual path used. + +### 2b. Post-migration source rename + +After live run **PASS**, rename the Codecept source so CI no longer picks it up: + +- `foo_test.js` → `foo_migrated.js` (same directory; `git mv`) +- Codecept glob `tests/**/*_test.js` excludes `*_migrated.js` + +Pending tracker rows still list `*_test.js` paths. Done rows should list `*_migrated.js`. + +Path aliases (from `e2e_tests/tsconfig.json`): `@fixtures/*`, `@interfaces/*`, `@helpers/*`, +`@components/*`, `@pages/*`, `@api/*`, `@valkey`. + +## 3. Registration points (edit these when adding new code) + +- POM fixtures: `e2e_tests/fixtures/pmmTest.ts` (add to the `base.extend<{...}>` type + factory). +- API clients: `e2e_tests/api/api.ts` (add `readonly xApi` field + constructor assignment). +- API paths: `e2e_tests/helpers/apiEndpoints.ts`. +- Timeouts enum: `e2e_tests/helpers/timeouts.ts` (`Timeouts.THIRTY_SECONDS`, etc.). Use these, never raw numbers. +- POMs extend: `e2e_tests/pages/base.page.ts` (abstract `builders/buttons/elements/inputs/messages`; constructor takes `page`). + +POM shape (from CONTRIBUTING.md): group locators in `buttons`, `elements`, `inputs`, `messages`, +`builders`; arrow-function methods only; `url` property for the page URL. + +## 4. Existing e2e_tests inventory (REUSE, do not recreate) + +Registered fixtures (`pmmTest.ts`): `settingsPage, agentsPage, cliHelper, credentials, dashboard, +grafanaHelper, mongoDbHelper, api, qanStoredMetrics, urlHelper, helpPage, servicesPage, tour, mocks, +leftNavigation, portalRemoval, queryAnalytics, nodesPage, realTimeAnalyticsPage, vacuumDashboardPage, +updatesPage, downloadsPage`. + +API clients (`api/api.ts`): `accessControlApi, alertingApi, backupsApi, grafanaApi, inventoryApi, +realTimeAnalyticsApi, serverApi, settingsApi`. + +Helpers (`helpers/`): `grafana.helper` (auth), `mongodb.helper`, `cli.helper` (docker exec / psql / +commands), `url.helper`, `metrics.helper`, `mocks.helper`, `credentials.helper`, `apiEndpoints`, +`timeouts`. + +Pages (`pages/`): `base.page`, `navigation.page`, `helpCenter.page`, `tour.page`, `updates.page`, +`downloads.page`, `portalRemoval.page`, `inventory/{services,agents,nodes}.page`, +`qan/{queryAnalytics,rta/realTimeAnalytics,storedMetrics/storedMetrics}.page`, `ha/settings.page`, +`dashboards/{dashboards.page,home,mysql/*,valkey/*,postgresql/vacuumDashboard,operating-system/*}`. + +Components (`components/`): dashboard panels only -> `dashboards/panels/{panel,table,gauge,barGauge, +stat,stateTime,text,timeSeries,barTime,polyStat}.component.ts` (+ `index.ts`). +NOTE: `@components/NotificationComponent` referenced by `mappings.md` does NOT exist yet -> create it +on first need (wraps the pop-up/alert verification: `[role="alert"], [role="status"]`). + +Interfaces (`interfaces/`): `grafana, grafanaPanel, dashboard, inventory, accessControl, execReturn`. + +## 5. Custom step -> Playwright mapping (from custom_steps.js + mappings.md) + +- `verifyPopUpMessage(message, t=30)` -> `@components/NotificationComponent`. Keep the component dumb: it should expose the locator (`[role="alert"],[role="status"]`) and a `close()` method (`[aria]`), assert text, close via (`[aria-label="Close alert"]`). The `expect(componet.message).toContainText(message)` MUST be written inline in the test. Do not hide `expect(()` inside the component. +- `verifyWarning(message, t=10)` -> assert on `[data-testid="data-testid Alert warning"]`. +- `verifyInvisible(sel, t)` -> `await expect(locator).toBeHidden({ timeout })`. +- `asyncWaitFor(fn, t)` -> `await expect.poll(async () => ..., { timeout })`. +- `getPopUpLocator/getSuccessPopUpLocator/getClosePopUpButtonLocator` -> NotificationComponent locators. +- `downloadZipFile` -> Do NOT port. Use playwright's `const res = await request.get(url); const buffer = await res.body();` Pass the buffer directly to `AdmZip(buffer)` rather than writing to disk. +- `readZipArchive/readFileInZipArchive/getFileLineCount` -> `@helpers/archive.helper.ts` (create if missing; uses `adm-zip`). **`readZipArchive` and `getFileLineCount` are reusable utilities** `seeEntriesInZip/dontSeeEntriesInZip` are thin `expect()` wrappers: do NOT put them in the helper. Write the assertion loop inline in the test using `readZipArchive` directly (e.g. `const entries = readZipArchive(pathOrBuffer); expect(entries.tocontain('file.log);`). Hiding `expect` in a helper violates the `NoExpectsInHelpers` rules and triggers the `playwright/expect-expect` lint error - never use `eslint-disable` to supress it; refactor instead. +- `buildUrlWithParams(url, params)` -> `@helpers/url.helper.ts` (maps env/node_name/cluster/service_name/application_name/database/columns/from/to/search/page_number/page_size/refresh/metric to `var-*`/query params; defaults from=now-5m,to=now). +- `signOut()` -> `await page.goto('graph/logout')`. +- `cleanupClickhouse()` -> `@helpers/cli.helper.ts`: `docker exec pmm-server clickhouse-client --database pmm --password clickhouse --query "TRUNCATE TABLE metrics"`. +- `seeElementsDisabled/seeElementsEnabled(locator)` -> `expect(locator).toHaveAttribute('disabled', ...)` / `toBeEnabled()`. +- `useDataQA(sel)` -> `getByTestId(sel)`. + +## 6. Commands + +Run all from `e2e_tests/`: + +- Install (first time): `npm ci` then `npx playwright install chromium`. +- TS compile check: `npx tsc --noEmit -p tsconfig.json`. +- Lint: `npx eslint .` (config: `eslint.config.mjs`; arrow functions only, numeric separators, camelCase filenames, no `.skip()`/commented tests). +- Run one migrated test with provisioning: from repo root, `./.cursor/scripts/run-migration-single-test.sh 'tests//.test.ts' '' `. +- Run by tag: `npx playwright test --grep "@"`. +- View failure trace: `npx playwright show-trace test-results//trace.zip`. +- The script exports `PMM_UI_URL=http://127.0.0.1/`, `ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin-password}`, `HEADLESS=true`, and `WORKERS=1`. + +## 7. Auth / login + +Tests authorize via `grafanaHelper.authorize()` in `pmmTest.beforeEach`. `pmmTest` already mocks +`/v1/users/me` (tour completed) and `/v1/server/updates` (no update) at context level. Basic auth +header helper: `GrafanaHelper.getToken()`. + +For **interactive locator discovery** (browser MCP fallback only), shared workflow docs remain under `.agents/workflows/`; use `.agents/workflows/pmmLogin.md` +— not the UI login form. See section 8. + +## 8. Fixing broken locators (trace first, MCP fallback) + +When a live run fails on a locator (timeout, not visible, strict mode violation): + +| Priority | Tool | When | +| --- | --- | --- | +| 1 | **Playwright trace** | Always try first. Config retains trace on first failure. `npx playwright show-trace `. Inspect DOM at the failing step; fix POM; re-run test. | +| 2 | **Browser MCP** | Only if trace is unavailable or target page/iframe not reached. Login per `pmmLogin.md`; navigate to POM `url`; **one** `browser_snapshot`/CDP pass per page; update POM; re-run. Rules: `mcpRules.md`. | + +Do not use playwright-cli for migration verification. Do not change test behavior to work around a bad +locator. Max **2** locator-fix loops per run (see `run.md` Step 7a). + +Trace path hint: failures write under `e2e_tests/test-results/`; open the `trace.zip` for the failed test. diff --git a/.cursor/skills/codeceptjs-migration/references/examples-pom.md b/.cursor/skills/codeceptjs-migration/references/examples-pom.md new file mode 100644 index 000000000..a1f768e8b --- /dev/null +++ b/.cursor/skills/codeceptjs-migration/references/examples-pom.md @@ -0,0 +1,51 @@ +# Migration Example: Page Object Model (POM) +This file provides a "Gold Standard" example of a CodeceptJS POM migrated to the Playwright BasePage structure. + +## ❌ Source: `tests/pages/pmmSettingsPage.js` +```javascript +module.exports = { + url: '/pmm-ui/settings', + fields: { + publicAddressInput: '$publicAddress-text-input', + applyButton: 'button[type="submit"]', + }, + async applyChanges() { + I.click(this.fields.applyButton); + I.verifyPopUpMessage(this.messages.successPopUpMessage, 30); + }, +}; +``` + +## ✅ Target: `e2e_tests/pages/ha/settings.page.ts` +```typescript +import BasePage from '../base.page'; +import pmmTest from '../../fixtures/pmmTest'; +import { expect } from '@playwright/test'; + +export default class SettingsPage extends BasePage { + url = '/pmm-ui/settings'; + + buttons = { + applyAdvancedChanges: this.page.getByTestId('advanced-button'), + }; + + inputs = { + publicAddress: this.page.getByTestId('text-input-public-address'), + }; + + async applyChanges(): Promise { + await pmmTest.step('Apply changes', async () => { + await this.buttons.apply.click(); + await this.notifications.verifyPopUpMessage(this.messages.successPopUpMessage, Timeouts.THIRTY_SECONDS); + }); + } +} +``` + +## 🗝️ Key Changes Explained: +1. **Class Structure**: Changed from a plain object to a class extending `BasePage`. +2. **Locators**: `$` shorthand $\rightarrow$ `this.page.getByTestId()`. +3. **Categorization**: Fields are now split into `buttons`, `inputs`, `elements`, and `urls` as per the project shape. +4. **Steps**: Logic is now wrapped in `pmmTest.step` for better reporting in Playwright. +5. **Behavior preserved**: `I.verifyPopUpMessage(successPopUpMessage, 30)` is kept via `@components/NotificationComponent`, not replaced by an error-absence check. +6. **Types**: Added TypeScript return types (`Promise`). diff --git a/.cursor/skills/codeceptjs-migration/references/examples-test.md b/.cursor/skills/codeceptjs-migration/references/examples-test.md new file mode 100644 index 000000000..b5c64c28d --- /dev/null +++ b/.cursor/skills/codeceptjs-migration/references/examples-test.md @@ -0,0 +1,50 @@ +# Migration Example: Test File +This file provides a a "Gold Standard" example of a CodeceptJS test migrated to Playwright. + +## ❌ Source: `tests/configuration/verifySettings_test.js` +```javascript +Feature('Settings Verification'); + +Before(async ({ I, settingsAPI }) => { + await I.Authorize(); + await settingsAPI.restoreSettingsDefaults(); +}); + +Scenario('Verify Public Address @settings', async ({ I, pmmSettingsPage }) => { + I.amOnPage(pmmSettingsPage.url); + await pmmSettingsPage.waitForPmmSettingsPageLoaded(); + I.fillField(pmmSettingsPage.fields.publicAddressInput, '1.2.3.4'); + I.click(pmmSettingsPage.fields.applyButton); + I.verifyPopUpMessage(pmmSettingsPage.messages.successPopUpMessage); + I.seeInField(pmmSettingsPage.fields.publicAddressInput, '1.2.3.4'); +}); +``` + +## ✅ Target: `e2e_tests/tests/configuration/verifySettings.test.ts` +```typescript +import { expect } from '@playwright/test'; +import pmmTest from '@fixtures/pmmTest'; + +pmmTest.beforeEach(async ({ api, grafanaHelper }) => { + await grafanaHelper.authorize(); + await api.settingsApi.restoreSettingsDefaults(); +}); + +pmmTest('Verify Public Address @settings', async ({ page, settingsPage, notifications }) => { + await page.goto(settingsPage.url); + await settingsPage.waitForPmmSettingsPageLoaded(); + + await settingsPage.inputs.publicAddress.fill('1.2.3.4'); + await settingsPage.buttons.apply.click(); + + await notifications.verifyPopUpMessage(settingsPage.messages.successPopUpMessage); + await expect(settingsPage.inputs.publicAddress).toHaveValue('1.2.3.4'); +}); +``` + +## 🗝️ Key Changes Explained: +1. **Fixtures**: `I` and `settingsAPI` $\rightarrow$ `{ page, settingsPage, api, grafanaHelper, notifications }`. +2. **Navigation**: `I.amOnPage` $\rightarrow$ `page.goto`. +3. **Interactions**: `I.fillField` $\rightarrow$ `.fill()`, `I.click` $\rightarrow$ `.click()` on the same submit/apply control. +4. **Assertions (behavior preserved)**: `I.verifyPopUpMessage(successPopUpMessage)` $\rightarrow$ `notifications.verifyPopUpMessage(...)` (`@components/NotificationComponent`); `I.seeInField` $\rightarrow$ `expect().toHaveValue()`. The success-popup assertion is kept, not swapped for an error-absence check. +5. **Structure**: Wrapped in `pmmTest` for granular reporting. No explanatory comments in the migrated test. diff --git a/.cursor/skills/codeceptjs-migration/references/mappings.md b/.cursor/skills/codeceptjs-migration/references/mappings.md new file mode 100644 index 000000000..80d5c1578 --- /dev/null +++ b/.cursor/skills/codeceptjs-migration/references/mappings.md @@ -0,0 +1,53 @@ +# MappingsAI +## Helpers +DoNotMigrateHelpers;MapToExistingPlaywrightHelpers. +`grafana_helper.js`->`@helpers/grafana.helper.ts`;fixture:`grafanaHelper`. +`mongoDB.js`->`@helpers/mongodb.helper.ts`;fixture:`mongoDbHelper`. +`PostgresqlDBHelper`->`@helpers/cli.helper.ts`;RunPsqlViaDockerExec. +`Mailosaur`->npm`mailosaur`. +`apiHelper.js`/`REST`->`@api/api.ts`(e.g.`api.settingsApi.getSettings()`). +`LocalStorageHelper`->`await page.evaluate(() => window.localStorage.setItem(...))`. +`FileHelper`/`FileSystem`->Node`fs`/`path`. +`ChaiWrapper`(`assert`)->`expect()`. +`linksHelper.js`->Inline/POM/`@helpers/apiEndpoints.ts`. +`I.verifyCommand()`->`@helpers/cli.helper.ts`;fixture:`cliHelper`. +`testdata/`->`e2e_tests/testdata/`;LoadVia`fs`Or`cliHelper`. +## CodeceptSyntax +`I.amOnPage(path)`->`await page.goto(path)`. +`I.click(locator)`->`await locator.click()`. +`I.fillField(locator,value)`->`await locator.fill(value)`. +`I.clearField(locator)`->`await locator.clear()`. +`I.attachFile(locator,path)`->`await locator.setInputFiles(path)`. +`I.see(text,locator)`->`await expect(locator).toContainText(text)`. +`I.seeTextEquals(text,locator)`->`await expect(locator).toHaveText(text)`. +`I.dontSeeElement(locator)`->`await expect(locator).toBeHidden()`. +`I.waitForVisible(locator,seconds)`->`await expect(locator).toBeVisible({ timeout })`. +`I.waitForText(text,seconds,locator)`->`await expect(locator).toContainText(text,{ timeout })`. +`I.seeNumberOfElements(locator,n)`->`await expect(locator).toHaveCount(n)`. +`I.grabTextFrom(locator)`->`await locator.textContent()`. +`I.grabTextFromAll(locator)`->`await locator.allTextContents()`. +`I.grabAttributeFrom(locator,attr)`->`await locator.getAttribute(attr)`. +`I.seeAttributesOnElements(locator,{ attr: val })`->`await expect(locator).toHaveAttribute(attr,val)`. +`I.seeCssPropertiesOnElements(locator,{ color: val })`->`const c=await locator.evaluate(el => getComputedStyle(el).color); expect(c).toBe(val)`. +`I.waitForFile(path,t)`/`I.seeFile(path)`->`expect(fs.existsSync(path)).toBe(true)`. +`I.seeInThisFile(text)`->`expect(fs.readFileSync(path,'utf-8')).toContain(text)`. +`tryTo(...)`->ExplicitConditionalLogicOr`try/catch`OnlyWhenIgnoringFailure. + +## Custom Steps +`verifyPopUpMessage`/`verifyWarning`->`@components/NotificationComponent`. +`getPopUpLocator`/`getSuccessPopUpLocator`->`@components/NotificationComponent`. +`verifyInvisible`->`expect(locator).toBeHidden()`. +`asyncWaitFor`->`expect.poll()`. +`downloadZipFile`/`readZipArchive`/`seeEntriesInZip`->`@helpers/archive.helper.ts`. +`buildUrlWithParams`->`@helpers/url.helper.ts`. +`signOut`->`page.goto('graph/logout')`. +`cleanupClickhouse`->`@helpers/cli.helper.ts`(via`docker exec`). + +## ESLintSuppressions +DisableCommentsRequire`-- reason`. +Timeout:`// eslint-disable-next-line playwright/no-wait-for-timeout -- `. +POMAssert:`// eslint-disable-next-line playwright/expect-expect -- inside POM`. +Locator:`// eslint-disable-next-line playwright/prefer-locator -- via builder`. +Conditional:`/* eslint-disable playwright/no-conditional-expect -- logic matches source */`...`/* eslint-enable ... */`. +## AuditChecklist +CompareOriginalSkill+SourceTestVsMigrated:Folders/POM/APIPath;Helpers;CodeceptSyntax;Hooks;DataLoops;Tags;Skipped/Retry;URLsInPOM;LocatorSemantics;Assertions;Timeouts;NoComments;LintRules;NoLogicLoss. \ No newline at end of file diff --git a/.cursor/skills/codeceptjs-migration/run.md b/.cursor/skills/codeceptjs-migration/run.md new file mode 100644 index 000000000..6c2ef0644 --- /dev/null +++ b/.cursor/skills/codeceptjs-migration/run.md @@ -0,0 +1,157 @@ +--- +description: Daily CodeceptJS -> Playwright migration run (one test per day, behavior-preserving, live-verified) +--- + +# Daily Migration Run + +Migrate exactly ONE CodeceptJS test per run into `e2e_tests`, verify it with `.cursor/scripts/run-migration-single-test.sh`, and update the tracker. Follow every step in order. Do not skip the confidence gate. Do not migrate more than one source test per run. + +## Inputs / references + +- Context pack: `.cursor/skills/codeceptjs-migration/context.md` (provisioning, repo map, registration points, existing inventory, commands). READ THIS FIRST. +- Tracker: `.cursor/skills/codeceptjs-migration/tracker.md` (pick the work item; update it at the end). +- Migration rules: `.cursor/skills/codeceptjs-migration/SKILL.md` + its `references/`. +- Live-run script: `.cursor/scripts/run-migration-single-test.sh` (fixed server image: `perconalab/pmm-server:3-dev-latest`). + +## STRICT rules + +1. Behavior preservation: the migrated Playwright test MUST reproduce the source test exactly - same flow, same `Before`/`After` setup/cleanup, same assertions and their semantics, same data-driven iterations, same tags, same locator targets. No added/removed/weakened/"improved" coverage, no invented waits. If a faithful mapping is impossible, STOP and report. +2. Confidence gate: after migration and BEFORE any live run, do a line-by-line critical analysis and emit a confidence %. Only run the test if confidence > 95% with zero unexplained discrepancies. Otherwise set the row to `needs-review` with the exact gaps and STOP. +3. Git/remote actions are not automatic. Do not create branches, commits, pushes, PRs, or Slack posts unless the user explicitly asks for them. + +## Procedure + +### Step 0 - Load context + +Read `.cursor/skills/codeceptjs-migration/context.md`. Do NOT re-scan the whole repo. + +### Step 1 - Select the work item + +Open `tracker.md`, pick the FIRST row with `status = pending` (top-to-bottom), and set it to `in-progress`. If there are no `pending` rows, report `migration backlog empty` and stop. + +### Step 2 - Confirm env and live-run arguments + +1. Read the source test's `Before`/`BeforeSuite`/`Data(...)` to confirm what DB/services it needs. Update the row's `Env`/`Setup` if the actual need differs from the planned value. +2. Derive the live-run command arguments: + - ``: migrated file path relative to `e2e_tests/`, for example `tests/configuration/pmmInventory.test.ts`. + - ``: the tracker `Setup` value, or an empty string when no DB/service provisioning is needed. + - ``: `true` only when the source test needs a standalone PMM Client/node outside `pmm-framework.py` provisioning; use `false` for pure UI tests and for DB/service tests where `pmm-framework.py` creates the monitored services. +3. The live-run script uses fixed `DOCKER_VERSION=perconalab/pmm-server:3-dev-latest` and `ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin-password}`. Use that same password for server readiness, PMM client setup, `pmm-framework.py`, and Playwright runtime. Override `ADMIN_PASSWORD=...` only when intentionally testing a different password. +4. If required infra cannot be created by local Docker/Cursor Cloud (cloud RDS/Aurora/Azure, AMI/OVF, external pmm-demo), set the row to `blocked-on-env`, record the missing infra in Notes, go to Step 8, and STOP. + +### Step 3 - Best-fit target (mandatory before writing code) + +1. Read the source scenarios' **behavior** (page URL, POMs, tags, hooks) — not just the Codecept filename. +2. Search `context.md` section 4 and the tracker reconciliation notes for an existing Playwright file that already covers the same page/feature/fixtures (e.g. help-page log download → `helpCenter.test.ts`, left-menu traversal → `navigation.test.ts`). +3. If an existing `e2e_tests` file is the best fit, **append** the migrated scenario(s) there. Do **not** create a new `*.test.ts` when a suitable file exists. +4. If coverage already exists with no gaps, mark the row `done` with Notes `already covered by ` and still rename the source (Step 8a), then STOP. +5. Only create a new Playwright test file when no best-fit target exists. Record the **actual** target path in the tracker `Target` column (may differ from the tracker's initial guess). + +### Step 4 - Migrate (behavior-preserving) + +Follow `SKILL.md`: +- Migrate the test file + its POMs (`*.page.ts` extending `BasePage`) + API clients (`*.api.ts`) + custom-step usages (map via `context.md` section 5 / `mappings.md`; create `NotificationComponent` and `archive.helper.ts` if first needed). +- Reuse existing fixtures/helpers/API/components from `context.md` section 4; do not recreate them. +- Register new POM fixtures in `fixtures/pmmTest.ts`, new API clients in `api/api.ts`, new API paths in `helpers/apiEndpoints.ts`. Use `Timeouts` for any explicit wait. +- Prefer surgical edits on existing files. No comments in migrated tests. Arrow functions only. +- Do NOT migrate `.retry(N)`; omit tests that are BOTH skipped AND commented out. + +### Step 5 - Static validation + +From `e2e_tests/`: `npx tsc --noEmit -p tsconfig.json` and `npx eslint .`. Both must pass before a live run. + +### Step 6 - Confidence gate (MANDATORY, before any run) + +Do the `CriticalAuditGate` from `SKILL.md`: line-by-line migrated-vs-source comparison covering flow, setup/cleanup hooks, every assertion, data loops, tags, locator semantics, and timeouts. Output the audit checklist + confidence %. +- If confidence > 95% and discrepancies = none -> continue to Step 7. +- Else -> set the row to `needs-review`, record the specific gaps in Notes, go to Step 8, and STOP. + +### Step 7 - Live run (script is canonical) + +From repo root, run exactly one migrated file through the Cursor script: + +```bash +chmod +x .cursor/scripts/run-migration-single-test.sh +./.cursor/scripts/run-migration-single-test.sh '' '' +``` + +Examples: + +```bash +./.cursor/scripts/run-migration-single-test.sh 'tests/helpCenter.test.ts' '' false +./.cursor/scripts/run-migration-single-test.sh 'tests/configuration/pmmInventory.test.ts' '--database pdpgsql' false +./.cursor/scripts/run-migration-single-test.sh 'tests/dashboards/nodesOverviewDashboard.test.ts' '' true +``` + +Retry once only if the failure is clearly transient. For locator / visibility failure, follow Step 7a before marking `failed`. You may loop Step 7 + 7a up to 2 locator-fix attempts. + +Then: +- PASS -> rename the Codecept source (Step 8a), set the row `done`, record confidence %, date, and live-run command. +- Hard FAIL -> set the row `failed`, record the root cause and failed command in Notes. Do **not** rename the source file. + +### Step 7a - Fix broken locators (trace first, MCP fallback) + +Behavior preservation still applies: fix the `Locator` so it targets the same element the Codecept source intended. Never weaken assertions or click a different control. + +1. Trace first: `playwright.config.ts` uses `trace: retain-on-first-failure`. Open `npx playwright show-trace test-results//trace.zip`, inspect the failing step, update the POM locator, and re-run Step 7. +2. Browser MCP fallback only when the trace is missing, empty, or does not show the target. Shared MCP workflow docs remain under `.agents/workflows/`: follow `.agents/workflows/mcpRules.md` and `.agents/workflows/pmmLogin.md`, do exactly one DOM discovery pass for the failing control, update the POM, and re-run Step 7. + +Locator rules: +- All POM entries must be Playwright `Locator` objects (`this.page.getByTestId(...)`, etc.), not strings. +- Reuse existing locators from `context.md` section 4 when the same page already exists in `e2e_tests`. +- `$foo` in Codecept often maps to a different rendered test id; confirm against trace/MCP DOM, not by guess. +- Chained `locate().find()` maps to chained `.locator()`; preserve scope. + +After a substantial POM fix, re-check confidence % before re-running. + +### Step 8a - Rename Codecept source (PASS only) + +CodeceptJS CI discovers tests via `tests/**/*_test.js` in `codeceptjs-e2e/pr.codecept.js`. After a **successful** live run, exclude the migrated file from workflows by renaming it in place: + +```bash +git mv codeceptjs-e2e/tests//_test.js codeceptjs-e2e/tests//_migrated.js +``` + +Example: `leftNavigation_test.js` → `leftNavigation_migrated.js`. Update the tracker `Source` column to the `_migrated.js` path. Keep the file as a migration reference; do not delete it. + +### Step 8 - Tracker + handoff + +1. Update the tracker row (status, confidence %, date, notes, and live-run command/result when applicable). +2. Report the outcome in chat with files changed, validation run, discrepancies, confidence %, and tracker status. + +### Step 9 - Definition of Done output + +Output the `SKILL.md` DoD sequence: Audit Checklist -> Verdict (PASS/FAIL) -> Final Report (Files Changed, Validation Run, Discrepancies, Confidence %). Then stop - one test per run. + +### Step 10 - Git branches and PR (on PASS only) + +See `context.md` §1f. Instructions live on **`PMM-7-codeceptjs-migration`**; test code merges to **`main`**. + +1. `git fetch origin main PMM-7-codeceptjs-migration` +2. Read tracker/skills from `PMM-7-codeceptjs-migration` (instructions source of truth). +3. `git checkout main && git pull origin main` +4. `git checkout -b migrate--` +5. Commit **only** migration artifacts: + - `e2e_tests/**` (tests, pages, helpers, api, package.json if needed) + - `codeceptjs-e2e/**` `*_test.js` → `*_migrated.js` rename (Step 8a) + - Do **not** commit `.cursor/skills/**` or `.cursor/scripts/**` on this branch. +6. Push the migrate branch and open a PR with **base `main`**: + +```bash +git push -u origin migrate-- +gh pr create --base main --head migrate-- \ + --title "migrate(): codeceptjs -> playwright" \ + --body "..." +``` + +Use `gh pr create --base main` explicitly (`open_git_pr` MCP may not set the base). Put the PR URL in the tracker row notes. + +7. Separately, push tracker/docs updates to **`PMM-7-codeceptjs-migration`**: + +```bash +git checkout PMM-7-codeceptjs-migration && git pull origin PMM-7-codeceptjs-migration +# commit .cursor/skills/codeceptjs-migration/tracker.md (and skill doc edits if any) +git push origin PMM-7-codeceptjs-migration +``` + +8. Post Slack summary (if automation is configured). \ No newline at end of file diff --git a/.cursor/skills/codeceptjs-migration/tracker.md b/.cursor/skills/codeceptjs-migration/tracker.md new file mode 100644 index 000000000..86260aae3 --- /dev/null +++ b/.cursor/skills/codeceptjs-migration/tracker.md @@ -0,0 +1,167 @@ +# CodeceptJS -> Playwright Migration Tracker + +One row per CodeceptJS source test (98 unique files). The daily automation takes the top row whose +status is `pending`, migrates it per `.cursor/skills/codeceptjs-migration/SKILL.md`, live-verifies +it, and updates the row. + +## How to use this tracker (automation contract) + +- Pick the first row (top-to-bottom) with `status = pending`. +- The `Env` column is the PLANNED provisioning; ALWAYS confirm it by reading the source test's + `Before`/`BeforeSuite` hook + `Data(...)` before provisioning. Update the row if it differs. +- `Setup` is the `setup_services` argument set for `pmm-framework.py` (see `context.md` sections 1c-1e). + Empty `Setup` = no DB provisioning needed (server + client only, or no client either). +- Ordering is efficiency-first: consecutive rows share the same env bucket so the provisioned PMM + environment can be reused day-to-day; within that, UI-only first, heaviest/integration last. +- Rows pre-set to `blocked-on-env` need infra the local host cannot create yet (cloud RDS/Aurora/Azure, + AMI/OVF images, the external pmm-demo server). Move them to `pending` once that infra is available. +- **Best-fit target:** before migrating, pick the existing Playwright file that matches source + *behavior* (page, feature, hooks). Only create a new `*.test.ts` when no fit exists. Update `Target` + to the actual file used. +- **Source rename on done:** after live PASS, `git mv` `*_test.js` → `*_migrated.js` so Codecept CI + (`tests/**/*_test.js`) skips it. Update `Source` column to the `_migrated.js` path. +- **Instructions branch:** `PMM-7-codeceptjs-migration` — read tracker/skills here; push tracker and + `.cursor/**` doc updates here only. +- **PR base branch:** `main` — per-test migrate PRs target `main`, not PMM-7. +- **PR scope:** `e2e_tests/**` and Codecept `*_migrated.js` renames only; never include `.cursor/**` + in a test PR. Branch migrate PRs from `main`. + +## Status legend + +`pending` -> not started | `in-progress` -> being migrated | `migrated` -> code done, not yet verified | +`needs-review` -> confidence <=95%, gaps listed in Notes | `done` -> live run PASSED | `failed` -> +live run failed (root cause in Notes) | `blocked-on-env` -> required infra unavailable. + +## Env bucket summary + +| Bucket | Env | Setup (setup_services) | +| --- | --- | --- | +| B1 | none (UI/server only) | (none) | +| B2 | pgsql / pdpgsql | `--database pgsql` or `--database pdpgsql` | +| B3 | ps / mysql | `--database ps=8.4` | +| B4 | psmdb | `--database psmdb` | +| B5 | valkey | `--database valkey` | +| B6 | pxc + haproxy | `--database haproxy --database ps --database pxc` | +| B7 | ssl variants | `--database ssl_mysql` / `ssl_psmdb` / `ssl_pdpgsql=16` | +| B8 | backup (ps + bucket) | `--database ps=8.4,BACKUP=true --database bucket` | +| B9 | qa-integration (multi-db) | per test (ps / psmdb / pdpgsql / pxc / pgss / pgsm) | +| B10 | migration | `--database ` (source) + pmm2->pmm3 flow | +| B11 | advisors | `--database pdpgsql` | +| B12 | upgrade (special harness) | pmm-server upgrade flow + `--database` | +| B13 | blocked (cloud/demo/ami/ovf) | external infra required | + +## Migration rows + +| # | Status | Bucket | Env | Setup | Source | Target | Tags | Conf% | Date | Notes/PR | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 1 | done | B1 | none | | `codeceptjs-e2e/tests/leftNavigation_migrated.js` | `e2e_tests/tests/helpCenter.test.ts` | @menu | 98% | 2026-07-07 | PMM-T1830; source renamed; https://github.com/percona/pmm-qa/pull/1054 | +| 2 | pending | B1 | none | | `codeceptjs-e2e/tests/serverLogs_test.js` | `e2e_tests/tests/serverLogs.test.ts` | @server-logs | | | logs.zip download | +| 3 | pending | B1 | none | | `codeceptjs-e2e/tests/verifyAnnotations_test.js` | `e2e_tests/tests/verifyAnnotations.test.ts` | @annotations | | | confirm needs client | +| 4 | pending | B1 | none | | `codeceptjs-e2e/tests/configuration/verifyPMMSettingsPageElements_test.js` | `e2e_tests/tests/configuration/settingsPageElements.test.ts` | @settings | | | reuse `settingsPage` | +| 5 | pending | B1 | none | | `codeceptjs-e2e/tests/configuration/verifyServerAdminSettings_test.js` | `e2e_tests/tests/configuration/serverAdminSettings.test.ts` | @settings | | | | +| 6 | pending | B1 | none | | `codeceptjs-e2e/tests/configuration/profile_test.js` | `e2e_tests/tests/configuration/profile.test.ts` | @settings | | | | +| 7 | pending | B1 | none | | `codeceptjs-e2e/tests/server-admin/verifyGrafanaIsGone_test.js` | `e2e_tests/tests/serverAdmin/grafanaIsGone.test.ts` | @server-admin | | | | +| 8 | pending | B1 | none | | `codeceptjs-e2e/tests/administration/serviceAccounts_test.js` | `e2e_tests/tests/administration/serviceAccounts.test.ts` | @service-accounts | | | | +| 9 | pending | B1 | none | | `codeceptjs-e2e/tests/dashboards/verifyHomeDashboards_test.js` | `e2e_tests/tests/dashboards/homeDashboards.test.ts` | @dashboards | | | reuse `dashboard` fixture | +| 10 | pending | B1 | none | | `codeceptjs-e2e/tests/dashboards/verifyPMMHealthDashboard_test.js` | `e2e_tests/tests/dashboards/pmmHealthDashboard.test.ts` | @dashboards | | | | +| 11 | pending | B1 | none | | `codeceptjs-e2e/tests/dashboards/verifySearchDashboards_test.js` | `e2e_tests/tests/dashboards/searchDashboards.test.ts` | @dashboards | | | | +| 12 | pending | B1 | none | | `codeceptjs-e2e/tests/dashboards/verifyNodesOverviewDashboard_test.js` | `e2e_tests/tests/dashboards/nodesOverviewDashboard.test.ts` | @dashboards | | | node metrics (client) | +| 13 | pending | B1 | none | | `codeceptjs-e2e/tests/verifyInsightDashboards_test.js` | `e2e_tests/tests/dashboards/insightDashboards.test.ts` | @dashboards | | | root-level; confirm vs dashboards/ dup | +| 14 | pending | B1 | none | | `codeceptjs-e2e/tests/dashboards/verifyInsightDashboards_test.js` | `e2e_tests/tests/dashboards/insightDashboardsExtended.test.ts` | @dashboards | | | reconcile with #13 (near-duplicate) | +| 15 | pending | B1 | none | | `codeceptjs-e2e/tests/verifyOSDashboards_test.js` | `e2e_tests/tests/dashboards/osDashboards.test.ts` | @dashboards | | | node exporter | +| 16 | pending | B1 | none | | `codeceptjs-e2e/tests/verifyVMDashboards_test.js` | `e2e_tests/tests/dashboards/vmDashboards.test.ts` | @dashboards @nightly | | | VictoriaMetrics | +| 17 | pending | B1 | none | | `codeceptjs-e2e/tests/metrics/explorePage_test.js` | `e2e_tests/tests/metrics/explorePage.test.ts` | @metrics | | | | +| 18 | pending | B1 | none | | `codeceptjs-e2e/tests/ia/common_test.js` | `e2e_tests/tests/ia/common.test.ts` | @ia | | | alerting UI | +| 19 | pending | B1 | none | | `codeceptjs-e2e/tests/ia/alerts_test.js` | `e2e_tests/tests/ia/alerts.test.ts` | @ia @fb-alerting | | | | +| 20 | pending | B1 | none | | `codeceptjs-e2e/tests/ia/ruleTemplates_test.js` | `e2e_tests/tests/ia/ruleTemplates.test.ts` | @fb-alerting | | | | +| 21 | pending | B1 | none | | `codeceptjs-e2e/tests/ia/alertRules_test.js` | `e2e_tests/tests/ia/alertRules.test.ts` | @fb-alerting | | | | +| 22 | pending | B1 | none | | `codeceptjs-e2e/tests/encryption/encryption_test.js` | `e2e_tests/tests/encryption/encryption.test.ts` | @encryption | | | confirm DB needs | +| 23 | pending | B1 | none | | `codeceptjs-e2e/tests/verifyDump_test.js` | `e2e_tests/tests/verifyDump.test.ts` | @dump | | | pmm dump | +| 24 | pending | B1 | none | | `codeceptjs-e2e/tests/verifyNomad_test.js` | `e2e_tests/tests/verifyNomad.test.ts` | @nomad | | | | +| 25 | pending | B1 | none | | `codeceptjs-e2e/tests/configuration/verifyPMMServerDisconnect_test.js` | `e2e_tests/tests/configuration/pmmServerDisconnect.test.ts` | @disconnect | | | | +| 26 | pending | B2 | pgsql | `--database pgsql` | `codeceptjs-e2e/tests/configuration/verifyPMMSettingsPageFunctionality_test.js` | `e2e_tests/tests/configuration/settingsPageFunctionality.test.ts` | @settings @stt | | | | +| 27 | pending | B2 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/configuration/verifyPMMInventory_test.js` | `e2e_tests/tests/configuration/pmmInventory.test.ts` | @inventory | | | reuse `servicesPage`/`agentsPage` | +| 28 | pending | B2 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/configuration/verifyPMMInventoryPagination_test.js` | `e2e_tests/tests/configuration/pmmInventoryPagination.test.ts` | @inventory | | | | +| 29 | pending | B2 | pgsql | `--database pgsql` | `codeceptjs-e2e/tests/QAN/common_test.js` | `e2e_tests/tests/qan/common.test.ts` | @qan | | | reuse `queryAnalytics` | +| 30 | pending | B2 | pgsql | `--database pgsql` | `codeceptjs-e2e/tests/QAN/overview_test.js` | `e2e_tests/tests/qan/overview.test.ts` | @qan | | | | +| 31 | pending | B2 | pgsql | `--database pgsql` | `codeceptjs-e2e/tests/QAN/filters_test.js` | `e2e_tests/tests/qan/filters.test.ts` | @qan | | | | +| 32 | pending | B2 | pgsql | `--database pgsql` | `codeceptjs-e2e/tests/QAN/pagination_test.js` | `e2e_tests/tests/qan/pagination.test.ts` | @qan | | | | +| 33 | pending | B2 | pgsql | `--database pgsql` | `codeceptjs-e2e/tests/QAN/timerange_test.js` | `e2e_tests/tests/qan/timerange.test.ts` | @qan | | | | +| 34 | pending | B2 | pgsql | `--database pgsql` | `codeceptjs-e2e/tests/QAN/query_test.js` | `e2e_tests/tests/qan/query.test.ts` | @qan | | | | +| 35 | pending | B2 | pgsql | `--database pgsql` | `codeceptjs-e2e/tests/QAN/details_test.js` | `e2e_tests/tests/qan/details.test.ts` | @qan | | | | +| 36 | pending | B2 | pgsql | `--database pgsql` | `codeceptjs-e2e/tests/QAN/details_explain_test.js` | `e2e_tests/tests/qan/detailsExplain.test.ts` | @qan | | | | +| 37 | pending | B2 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/dashboards/verifyPostgresqlDashboards_test.js` | `e2e_tests/tests/dashboards/postgresqlDashboards.test.ts` | @dashboards | | | | +| 38 | pending | B2 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/dockerConfiguration/externalPostgres_test.js` | `e2e_tests/tests/dockerConfiguration/externalPostgres.test.ts` | @docker-configuration | | | external PG for pmm-server | +| 39 | pending | B3 | ps | `--database ps=8.4` | `codeceptjs-e2e/tests/verifyMysqlDashboards_test.js` | `e2e_tests/tests/dashboards/mysqlDashboards.test.ts` | @dashboards | | | reuse existing mysql POMs | +| 40 | pending | B3 | ps | `--database ps=8.4` | `codeceptjs-e2e/tests/metrics/verifyMysqlLogLevel_test.js` | `e2e_tests/tests/metrics/mysqlLogLevel.test.ts` | @metrics | | | | +| 41 | pending | B4 | psmdb | `--database psmdb` | `codeceptjs-e2e/tests/verifyMongodbDashboards_test.js` | `e2e_tests/tests/dashboards/mongodbDashboards.test.ts` | @dashboards | | | reuse `mongoDbHelper` | +| 42 | pending | B4 | psmdb | `--database psmdb` | `codeceptjs-e2e/tests/metrics/verifyMongoDB_test.js` | `e2e_tests/tests/metrics/mongodb.test.ts` | @metrics | | | | +| 43 | pending | B4 | psmdb | `--database psmdb` | `codeceptjs-e2e/tests/metrics/verifyMongoDBCollectionFlags_test.js` | `e2e_tests/tests/metrics/mongodbCollectionFlags.test.ts` | @metrics | | | | +| 44 | pending | B4 | psmdb | `--database psmdb` | `codeceptjs-e2e/tests/metrics/verifyMongoDBExperimental_test.js` | `e2e_tests/tests/metrics/mongodbExperimental.test.ts` | @metrics | | | | +| 45 | pending | B4 | psmdb | `--database psmdb,SETUP_TYPE=shards` | `codeceptjs-e2e/tests/dashboards/verifyMongodbPbmDashboard_test.js` | `e2e_tests/tests/dashboards/mongodbPbmDashboard.test.ts` | @dashboards | | | PBM backup dashboard | +| 46 | pending | B5 | valkey | `--database valkey` | `codeceptjs-e2e/tests/dashboards/verifyValkeyDashboards_test.js` | `e2e_tests/tests/dashboards/valkey/valkeyDashboardsExtra.test.ts` | @dashboards @pmm-valkey-integration | | | reuse valkey POMs | +| 47 | pending | B6 | pxc+haproxy | `--database haproxy --database ps --database pxc` | `codeceptjs-e2e/tests/qa-integration/pmm_pxc_integration_test.js` | `e2e_tests/tests/integration/pxc.test.ts` | @pmm-ps-pxc-haproxy-integration | | | | +| 48 | pending | B7 | ssl_mysql | `--database ssl_mysql` | `codeceptjs-e2e/tests/verifyTLSMySQLRemoteInstance_test.js` | `e2e_tests/tests/remoteInstances/tlsMysql.test.ts` | @ssl-mysql | | | @not-ui-pipeline | +| 49 | pending | B7 | ssl_psmdb | `--database ssl_psmdb` | `codeceptjs-e2e/tests/verifyTLSMongoDBRemoteInstance_test.js` | `e2e_tests/tests/remoteInstances/tlsMongodb.test.ts` | @ssl-mongo | | | | +| 50 | pending | B7 | ssl_pdpgsql | `--database ssl_pdpgsql=16` | `codeceptjs-e2e/tests/verifyTLSPostgresRemoteInstance_test.js` | `e2e_tests/tests/remoteInstances/tlsPostgres.test.ts` | @ssl-postgres | | | | +| 51 | pending | B8 | ps+bucket | `--database ps=8.4,BACKUP=true --database bucket` | `codeceptjs-e2e/tests/backup/locations_test.js` | `e2e_tests/tests/backup/locations.test.ts` | @backup | | | MinIO bucket | +| 52 | pending | B8 | ps+bucket | `--database ps=8.4,BACKUP=true --database bucket` | `codeceptjs-e2e/tests/backup/inventory_test.js` | `e2e_tests/tests/backup/inventory.test.ts` | @backup | | | reuse `backupsApi` | +| 53 | pending | B8 | ps+bucket | `--database ps=8.4,BACKUP=true --database bucket` | `codeceptjs-e2e/tests/backup/scheduled_test.js` | `e2e_tests/tests/backup/scheduled.test.ts` | @backup | | | | +| 54 | pending | B8 | ps+bucket | `--database ps=8.4,BACKUP=true --database bucket` | `codeceptjs-e2e/tests/backup/mysql/inventory_mysql_test.js` | `e2e_tests/tests/backup/mysql/inventoryMysql.test.ts` | @backup | | | | +| 55 | pending | B8 | ps+bucket | `--database ps=8.4,BACKUP=true --database bucket` | `codeceptjs-e2e/tests/backup/mysql/scheduled_mysql_test.js` | `e2e_tests/tests/backup/mysql/scheduledMysql.test.ts` | @backup | | | | +| 56 | pending | B9 | ps | `--database ps` | `codeceptjs-e2e/tests/qa-integration/pmm_ps_integration_test.js` | `e2e_tests/tests/integration/ps.test.ts` | @pmm-ps-integration @not-ui-pipeline | | | | +| 57 | pending | B9 | ps,replication | `--database ps,SETUP_TYPE=replication` | `codeceptjs-e2e/tests/qa-integration/pmm_ps_replica_integration_test.js` | `e2e_tests/tests/integration/psReplica.test.ts` | @not-ui-pipeline | | | | +| 58 | pending | B9 | psmdb | `--database psmdb` | `codeceptjs-e2e/tests/qa-integration/pmm_psmdb_integration_test.js` | `e2e_tests/tests/integration/psmdb.test.ts` | @pmm-psmdb-*-integration @not-ui-pipeline | | | arbiter/replica/regular variants | +| 59 | pending | B9 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/qa-integration/pmm_pdpgsql_integration_test.js` | `e2e_tests/tests/integration/pdpgsql.test.ts` | @not-ui-pipeline | | | | +| 60 | pending | B9 | pgsql,pgss | `--database pgsql,QUERY_SOURCE=pgstatements` | `codeceptjs-e2e/tests/qa-integration/pmm_pgss_integration_test.js` | `e2e_tests/tests/integration/pgss.test.ts` | @not-ui-pipeline | | | pg_stat_statements | +| 61 | pending | B9 | pdpgsql,pgsm | `--database pdpgsql,PGSM_BRANCH=...` | `codeceptjs-e2e/tests/qa-integration/pmm_pgsm_integration_test.js` | `e2e_tests/tests/integration/pgsm.test.ts` | @not-ui-pipeline | | | pg_stat_monitor | +| 62 | pending | B10 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/migration/pdpgsql_test.js` | `e2e_tests/tests/migration/pdpgsql.test.ts` | @migration | | | pmm2->pmm3 migration flow | +| 63 | pending | B10 | ps | `--database ps` | `codeceptjs-e2e/tests/migration/ps_test.js` | `e2e_tests/tests/migration/ps.test.ts` | @migration | | | | +| 64 | pending | B10 | psmdb | `--database psmdb` | `codeceptjs-e2e/tests/migration/psmdb_test.js` | `e2e_tests/tests/migration/psmdb.test.ts` | @migration | | | | +| 65 | pending | B11 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/advisors/advisors_test.js` | `e2e_tests/tests/advisors/advisors.test.ts` | @advisors | | | | +| 66 | pending | B11 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/advisors/stt/sttSettings_test.js` | `e2e_tests/tests/advisors/sttSettings.test.ts` | @stt | | | | +| 67 | pending | B11 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/advisors/stt/allChecks_test.js` | `e2e_tests/tests/advisors/allChecks.test.ts` | @stt | | | | +| 68 | pending | B11 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/advisors/stt/databaseChecks_test.js` | `e2e_tests/tests/advisors/databaseChecks.test.ts` | @stt | | | | +| 69 | pending | B11 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/advisors/stt/checksExecution_test.js` | `e2e_tests/tests/advisors/checksExecution.test.ts` | @stt | | | | +| 70 | pending | B11 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/advisors/v2/configuration_test.js` | `e2e_tests/tests/advisors/v2Configuration.test.ts` | @advisors | | | | +| 71 | pending | B1 | none | | `codeceptjs-e2e/tests/configuration/permissions_test.js` | `e2e_tests/tests/configuration/permissions.test.ts` | @permissions @grafana-pr | | | roles/permissions UI | +| 72 | pending | B1 | none | | `codeceptjs-e2e/tests/configuration/verifyRoleBasedAccessControl_test.js` | `e2e_tests/tests/configuration/rbac.test.ts` | @rbac | | | reuse `accessControlApi` | +| 73 | pending | B1 | none | | `codeceptjs-e2e/tests/verifyAddInstance_test.js` | `e2e_tests/tests/inventory/addInstance.test.ts` | @instances | | | add-instance UI | +| 74 | pending | B1 | none | | `codeceptjs-e2e/tests/verifyRemoteInstances_test.js` | `e2e_tests/tests/inventory/remoteInstances.test.ts` | @fb-instances | | | external exporter/HAProxy UI | +| 75 | pending | B2 | pdpgsql | `--database pdpgsql` | `codeceptjs-e2e/tests/dashboards/verifyGCRemoteInstance_test.js` | `e2e_tests/tests/dashboards/gcRemoteInstance.test.ts` | @dashboards | | | Google Cloud remote; confirm infra | +| 76 | pending | B4 | psmdb | `--database psmdb` | `codeceptjs-e2e/tests/verifyPTSummaryPanels_test.js` | `e2e_tests/tests/dashboards/ptSummaryPanels.test.ts` | @dashboards @pt-summary-nightly | | | multi-db summary | +| 77 | pending | B3 | ps | `--database ps=8.4` | `codeceptjs-e2e/tests/perf_test.js` | `e2e_tests/tests/perf.test.ts` | @perf | | | performance; confirm scope | +| 78 | pending | B1 | none | | `codeceptjs-e2e/tests/dockerConfiguration/verifySrvDataDirectory_test.js` | `e2e_tests/tests/dockerConfiguration/srvDataDirectory.test.ts` | @docker-configuration | | | may exist as srvFolder.test.ts - reconcile | +| 79 | pending | B1 | none | | `codeceptjs-e2e/tests/dockerConfiguration/publicAddressVariable_test.js` | `e2e_tests/tests/dockerConfiguration/publicAddressVariable.test.ts` | @docker-configuration | | | recreates pmm-server with env var | +| 80 | pending | B12 | upgrade | pmm-server upgrade flow | `codeceptjs-e2e/tests/upgrade/upgradePMM_test.js` | `e2e_tests/tests/upgrade/upgradePmm.test.ts` | @pmm-upgrade | | | special harness (old->new server) | +| 81 | pending | B12 | upgrade | pmm-server upgrade flow | `codeceptjs-e2e/tests/upgrade/customPassword_test.js` | `e2e_tests/tests/upgrade/customPassword.test.ts` | @pmm-upgrade | | | | +| 82 | pending | B12 | upgrade | pmm-server upgrade flow | `codeceptjs-e2e/tests/upgrade/dashboards_test.js` | `e2e_tests/tests/upgrade/dashboards.test.ts` | @pmm-upgrade | | | | +| 83 | pending | B12 | upgrade | pmm-server upgrade flow | `codeceptjs-e2e/tests/upgrade/settingsMetrics_test.js` | `e2e_tests/tests/upgrade/settingsMetrics.test.ts` | @pmm-upgrade | | | | +| 84 | pending | B12 | upgrade | pmm-server upgrade flow | `codeceptjs-e2e/tests/upgrade/externalService_test.js` | `e2e_tests/tests/upgrade/externalService.test.ts` | @pmm-upgrade | | | | +| 85 | pending | B12 | upgrade | pmm-server upgrade flow | `codeceptjs-e2e/tests/upgrade/ssl_test.js` | `e2e_tests/tests/upgrade/ssl.test.ts` | @pmm-upgrade | | | | +| 86 | pending | B12 | upgrade | pmm-server upgrade flow | `codeceptjs-e2e/tests/upgrade/advisorsAlerting_test.js` | `e2e_tests/tests/upgrade/advisorsAlerting.test.ts` | @pmm-upgrade | | | | +| 87 | pending | B12 | upgrade | pmm-server upgrade flow | `codeceptjs-e2e/tests/upgrade/annotationsPrometheus_test.js` | `e2e_tests/tests/upgrade/annotationsPrometheus.test.ts` | @pmm-upgrade | | | | +| 88 | blocked-on-env | B13 | cloud | AWS RDS instance required | `codeceptjs-e2e/tests/verifyAWSRDSMySQLInstance_test.js` | `e2e_tests/tests/remoteInstances/awsRdsMysql.test.ts` | @instances | | | needs AWS creds + RDS | +| 89 | blocked-on-env | B13 | cloud | AWS RDS instance required | `codeceptjs-e2e/tests/verifyAWSRDSPostgreSQLInstance_test.js` | `e2e_tests/tests/remoteInstances/awsRdsPostgres.test.ts` | @instances | | | needs AWS creds + RDS | +| 90 | blocked-on-env | B13 | cloud | Aurora instance required | `codeceptjs-e2e/tests/verifyAuroraMySQLRemoteInstance_test.js` | `e2e_tests/tests/remoteInstances/auroraMysql.test.ts` | @instances | | | needs AWS Aurora | +| 91 | blocked-on-env | B13 | cloud | Aurora instance required | `codeceptjs-e2e/tests/verifyAuroraPostgreSQLRemoteInstance_test.js` | `e2e_tests/tests/remoteInstances/auroraPostgres.test.ts` | @instances | | | needs AWS Aurora | +| 92 | blocked-on-env | B13 | cloud | Azure instance required | `codeceptjs-e2e/tests/verifyAzureMySQLPostgreSQLRemoteInstance_test.js` | `e2e_tests/tests/remoteInstances/azureMysqlPostgres.test.ts` | @instances | | | needs Azure creds | +| 93 | blocked-on-env | B13 | ami | AMI image required | `codeceptjs-e2e/tests/verifyInstanceIdAMISetup_test.js` | `e2e_tests/tests/ami/instanceIdAmiSetup.test.ts` | @ami | | | needs AWS AMI | +| 94 | blocked-on-env | B13 | ami/ovf | AMI+OVF images required | `codeceptjs-e2e/tests/upgrade/amiOvfUpgrade_test.js` | `e2e_tests/tests/upgrade/amiOvfUpgrade.test.ts` | @pmm-upgrade | | | needs AMI/OVF | +| 95 | blocked-on-env | B13 | ovf | OVF image required | `codeceptjs-e2e/tests/sshOVF_test.js` | `e2e_tests/tests/ovf/sshOvf.test.ts` | @ovf | | | needs OVF VM + SSH | +| 96 | blocked-on-env | B13 | pmm-demo | external pmm-demo server | `codeceptjs-e2e/tests/verifyPMMDemoDashboards_test.js` | `e2e_tests/tests/demo/pmmDemoDashboards.test.ts` | @pmm-demo @not-ui-pipeline | | | pmm-demo.percona.com | +| 97 | blocked-on-env | B13 | pmm-demo | external pmm-demo server | `codeceptjs-e2e/tests/verifyPMMDemoPermissionChecks_test.js` | `e2e_tests/tests/demo/pmmDemoPermissionChecks.test.ts` | @pmm-demo @not-ui-pipeline | | | | +| 98 | pending | B2 | pgsql | `--database pgsql` | `codeceptjs-e2e/tests/QAN/externalClickhouse_test.js` | `e2e_tests/tests/qan/externalClickhouse.test.ts` | @qan | | | external ClickHouse for QAN storage | + +## Notes on reconciliation + +- **Best-fit over filename:** the tracker `Target` column is a hint; always confirm by reading the + source scenarios. Place into an existing Playwright file when behavior matches (e.g. help-page tests + → `helpCenter.test.ts`, nav → `navigation.test.ts`). Create new files only when no fit exists. +- Some targets may already partially exist in `e2e_tests/tests/` (e.g. QAN under `tests/qan/rta/`, + valkey dashboards, docker `srvFolder.test.ts`, `clickHouse.test.ts`). On the migration day, first + check the target folder; if a test already covers the source, mark the row `done` with a note + instead of duplicating. Rows #13/#14 (Insight dashboards root vs dashboards/) are likely + near-duplicates - reconcile into one target. +- `@not-ui-pipeline` / cloud / demo / ami / ovf tests were excluded from the local-runnable buckets + because they need infra beyond a local docker PMM. They are pre-marked `blocked-on-env`. diff --git a/codeceptjs-e2e/tests/leftNavigation_test.js b/codeceptjs-e2e/tests/leftNavigation_migrated.js similarity index 100% rename from codeceptjs-e2e/tests/leftNavigation_test.js rename to codeceptjs-e2e/tests/leftNavigation_migrated.js diff --git a/e2e_tests/api/api.ts b/e2e_tests/api/api.ts index 96db45a88..8aeae6b09 100644 --- a/e2e_tests/api/api.ts +++ b/e2e_tests/api/api.ts @@ -25,6 +25,7 @@ export default class Api { this.inventoryApi = new InventoryApi(request); this.grafanaApi = new GrafanaApi(page, request); this.realTimeAnalyticsApi = new RealTimeAnalyticsApi(request); + this.serverApi = new ServerApi(request); this.settingsApi = new SettingsApi(request); this.serverApi = new ServerApi(request); } diff --git a/e2e_tests/helpers/apiEndpoints.ts b/e2e_tests/helpers/apiEndpoints.ts index cf64e76bf..0a080fb86 100644 --- a/e2e_tests/helpers/apiEndpoints.ts +++ b/e2e_tests/helpers/apiEndpoints.ts @@ -34,6 +34,7 @@ const apiEndpoints = { readyz: '/v1/server/readyz', settings: '/v1/server/settings', updates: '**/v1/server/updates?force=**', + version: '/v1/version', }, users: { me: '**/v1/users/me', diff --git a/e2e_tests/helpers/archive.helper.ts b/e2e_tests/helpers/archive.helper.ts new file mode 100644 index 000000000..699871425 --- /dev/null +++ b/e2e_tests/helpers/archive.helper.ts @@ -0,0 +1,24 @@ +import AdmZip from 'adm-zip'; +import { expect } from '@playwright/test'; + +export const readZipArchive = (filepath: string): string[] => { + const zip = new AdmZip(filepath); + + return zip.getEntries().map(({ entryName }) => entryName); +}; + +export const seeEntriesInZip = async (filepath: string, entriesArray: string[]): Promise => { + const entries = readZipArchive(filepath); + + for (const entry of entriesArray) { + expect(entries, `Zip file: '${filepath}' must include: ${entriesArray}`).toContain(entry); + } +}; + +export const dontSeeEntriesInZip = async (filepath: string, entriesArray: string[]): Promise => { + const entries = readZipArchive(filepath); + + for (const entry of entriesArray) { + expect(entries, `'${entry}' must not be in ${entries}`).not.toContain(entry); + } +}; diff --git a/e2e_tests/package-lock.json b/e2e_tests/package-lock.json index 8b0dc4c45..9a29301a4 100644 --- a/e2e_tests/package-lock.json +++ b/e2e_tests/package-lock.json @@ -14,6 +14,7 @@ "@eslint/js": "^9.37.0", "@playwright/test": "^1.56.0", "@stylistic/eslint-plugin": "^2.3.0", + "@types/adm-zip": "^0.5.8", "@types/node": "^24.8.0", "@types/shelljs": "^0.8.17", "@typescript-eslint/eslint-plugin": "^8.46.1", @@ -558,6 +559,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/adm-zip": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.8.tgz", + "integrity": "sha512-RVVH7QvZYbN+ihqZ4kX/dMiowf6o+Jk1fNwiSdx0NahBJLU787zkULhGhJM8mf/obmLGmgdMM0bXsQTmyfbR7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", diff --git a/e2e_tests/package.json b/e2e_tests/package.json index bf94d04a7..c4917a2db 100644 --- a/e2e_tests/package.json +++ b/e2e_tests/package.json @@ -17,6 +17,7 @@ "@eslint/js": "^9.37.0", "@playwright/test": "^1.56.0", "@stylistic/eslint-plugin": "^2.3.0", + "@types/adm-zip": "^0.5.8", "@types/node": "^24.8.0", "@types/shelljs": "^0.8.17", "@typescript-eslint/eslint-plugin": "^8.46.1", diff --git a/e2e_tests/pages/dashboards/operating-system/index.ts b/e2e_tests/pages/dashboards/operating-system/index.ts index 380f37677..57e76a084 100644 --- a/e2e_tests/pages/dashboards/operating-system/index.ts +++ b/e2e_tests/pages/dashboards/operating-system/index.ts @@ -5,6 +5,7 @@ const OperatingSystemDashboards = { nodeSummary: new NodeSummaryDashboard(), }; -export type OperatingSystemDashboardsType = typeof OperatingSystemDashboards & Record; +export type OperatingSystemDashboardsType = typeof OperatingSystemDashboards & + Record; export default OperatingSystemDashboards; diff --git a/e2e_tests/tests/dashboards/valkey/valkeyDashboards.test.ts b/e2e_tests/tests/dashboards/valkey/valkeyDashboards.test.ts index b8ba1516e..b449b627f 100644 --- a/e2e_tests/tests/dashboards/valkey/valkeyDashboards.test.ts +++ b/e2e_tests/tests/dashboards/valkey/valkeyDashboards.test.ts @@ -13,6 +13,9 @@ for (const dashboardName in ValkeyDashboards) { const serviceList = await api.inventoryApi.getServicesByType(ServiceType.valkey); const cluster = serviceList[0].cluster; const dashboardPage = dashboard.valkey[dashboardName]; + const metrics = Array.isArray(dashboardPage.metrics) + ? dashboardPage.metrics + : dashboardPage.metrics(serviceList[0].service_name); await page.goto( urlHelper.buildUrlWithParameters(dashboardPage.url, { @@ -20,9 +23,9 @@ for (const dashboardName in ValkeyDashboards) { from: 'now-5m', }), ); - await dashboard.verifyMetricsPresent(dashboardPage.metrics, serviceList); + await dashboard.verifyMetricsPresent(metrics, serviceList); await dashboard.verifyAllPanelsHaveData([]); - await dashboard.verifyPanelValues(dashboardPage.metrics, serviceList); + await dashboard.verifyPanelValues(metrics, serviceList); }, ); } diff --git a/e2e_tests/tests/helpCenter.test.ts b/e2e_tests/tests/helpCenter.test.ts index 83ed6105c..1a4904118 100644 --- a/e2e_tests/tests/helpCenter.test.ts +++ b/e2e_tests/tests/helpCenter.test.ts @@ -1,5 +1,6 @@ import pmmTest from '@fixtures/pmmTest'; import { expect } from '@playwright/test'; +import { dontSeeEntriesInZip, seeEntriesInZip } from '@helpers/archive.helper'; import { Timeouts } from '@helpers/timeouts'; pmmTest.beforeEach(async ({ grafanaHelper, page }) => { @@ -66,6 +67,22 @@ pmmTest('PMM-T2119 - Verify export logs button @new-navigation', async ({ helpPa }); }); +/* eslint-disable-next-line playwright/expect-expect -- zip entry assertions live in archive.helper */ +pmmTest('PMM-T1830 - Verify downloading server diagnostics logs @menu', async ({ api, helpPage }) => { + const download = await helpPage.exportLogs(); + const path = await download.path(); + + if (!path) { + throw new Error('Download path is null'); + } + + await seeEntriesInZip(path, ['pmm-agent.yaml', 'pmm-managed.log', 'pmm-agent.log']); + + if ((await api.serverApi.getPmmVersion()).minor > 40) { + await dontSeeEntriesInZip(path, ['alertmanager.yml', 'alertmanager.base.yml']); + } +}); + pmmTest('PMM-T2120 - Verify start pmm tour button @new-navigation', async ({ helpPage }) => { await pmmTest.step('Verify starting PMM tour', async () => { await expect(helpPage.buttons.startPmmTour).toBeVisible(); diff --git a/e2e_tests/tsconfig.json b/e2e_tests/tsconfig.json index c9e16b283..a28c9fa5d 100644 --- a/e2e_tests/tsconfig.json +++ b/e2e_tests/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "baseUrl": ".", "target": "ESNext", "module": "CommonJS", "moduleResolution": "node",