Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 33 additions & 14 deletions .agents/workflows/pmmLogin.md
Original file line number Diff line number Diff line change
@@ -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`.
52 changes: 52 additions & 0 deletions .cursor/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions .cursor/environment.json
Original file line number Diff line number Diff line change
@@ -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"
}
56 changes: 56 additions & 0 deletions .cursor/scripts/run-migration-single-test.sh
Original file line number Diff line number Diff line change
@@ -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 <tests/foo.test.ts> [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"
61 changes: 61 additions & 0 deletions .cursor/scripts/start-docker-microvm.sh
Original file line number Diff line number Diff line change
@@ -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}}'))"
28 changes: 28 additions & 0 deletions .cursor/scripts/wait-pmm-ready.sh
Original file line number Diff line number Diff line change
@@ -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
120 changes: 120 additions & 0 deletions .cursor/skills/codeceptjs-migration/SKILL.md
Original file line number Diff line number Diff line change
@@ -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/<category>/*_test.js`->`e2e_tests/tests/<category>/*.test.ts`.
POMs:`tests/pages/`->`e2e_tests/pages/<category>/*.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/<path>/<name>_test.js`→`<name>_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.
Loading
Loading