test(e2e): review, flakes, no unsolicited e2e (#27514)

Co-authored-by: sudlud <[email protected]>
This commit is contained in:
Anton Popovichenko
2026-09-09 17:41:34 +02:00
committed by GitHub
co-authored by sudlud
parent 862a6615e6
commit 19ca6e1128
26 changed files with 176 additions and 141 deletions
+20 -15
View File
@@ -1,6 +1,13 @@
# E2E policy
Mandatory when writing or changing live-stack e2e (`e2e/`). **When** to write tests. **How:** AzerothGhost `LLM_GUIDE.md` / `EXAMPLES.md` (this file does not replace them). Inventory: `e2e/README.md`. Stack: AC 3.3.5a auth+world+MySQL; `E2E_*` may point at a gateway.
**Do not add, promote, or invent live-stack e2e tests unless the user explicitly asked.**
Do not write `e2e/local/` scratch tests to validate a fix. Existing tests that fail after a
behaviour change may be updated. Scratch, if any, stays in `e2e/local/` (gitignored) and is
never committed. Do not mention missing coverage unless the user asked about it.
The rest of this file applies only when the user asked to change `e2e/`, or an existing test
broke. **How:** AzerothGhost `LLM_GUIDE.md` / `EXAMPLES.md` (this file does not replace them).
Inventory: `e2e/README.md`. Stack: AC 3.3.5a auth+world+MySQL; `E2E_*` may point at a gateway.
## Terms
@@ -19,17 +26,15 @@ First match wins:
| If | Then |
|----|------|
| Pure calc / parse / bit math / no world | UNIT. NEVER e2e. |
| Covered by existing unit/mocks (`SpellProc*`, …) without login/map/DBC fidelity | UNIT or in-process integration. E2E only if unit cannot reach the path. |
| Covered by existing unit/mocks (`SpellProc*`, …) without login/map/DBC fidelity | UNIT or in-process integration. No new e2e unless asked. |
| SQL-only static (text, displayid, non-script loot) | No e2e. Manual optional. |
| SmartAI / conditions / waypoints; outcome only in-world | E2E SHOULD if player-visible and deterministic; else document manual repro. |
| Multi-system on a live session, or protocol/client-observed, or player-repro bugfix | E2E MUST (or update existing). Unit MAY still cover pure pieces. |
| Refactor, no behaviour intent | Update tests that break. Add e2e only if a critical system below lacks an oracle. |
| SmartAI / conditions / waypoints; outcome only in-world | No new e2e unless asked. Document a manual repro if useful. |
| Multi-system on a live session, or protocol/client-observed, or player-repro bugfix | No new e2e unless asked. Unit MAY still cover pure pieces. |
| Refactor, no behaviour intent | Update tests that break. Do not add e2e. |
Bias: player-visible and MVT fits → prefer e2e over unit-only. Not a MUST trigger and unsure → do not invent e2e; file a gap.
Do not invent e2e because a change is player-visible. File a gap only if the user asked for coverage.
**MUST add or update e2e** when any of: player-visible bugfix (encode the repro); protocol change; multi-system (combat/aura/death/quest/loot/mount/pet/vehicle/instance/PvP/guild/relog/DB after `.save`); crash/hang/freeze on client action (`ProbeWorldAlive` / `AssertWorldAlive`); existing `TestAC_*` path touched (update, do not drop); critical system with no oracle; claimed blizzlike fix for a tracked issue (`ConfirmedBugf` until fixed).
**MUST NOT** treat “tested in-game” as a substitute when MVT is feasible.
**Update** an existing test when the user asked or that test broke. Do not add a new suite, `TestAC_*`, or `e2e/local/` scratch test unless the user asked.
Harness cannot express it: extend harness or file a harness gap; inventory the gap; still unit-test pure logic.
@@ -106,7 +111,7 @@ Use these, not bare `t.Fatalf`, for classified outcomes:
## Critical systems
A PR that implements, fixes, or refactors a row MUST add or update e2e for that oracle, or keep an existing test that still asserts it. If harness cannot: gap in `e2e/README.md` + unit for pure logic.
Do not add e2e for a row just because a PR touches that system. Update an existing test only if it broke or the user asked. If harness cannot express something the user asked for: gap in `e2e/README.md` + unit for pure logic.
| System | Oracle / drive |
|--------|----------------|
@@ -129,11 +134,11 @@ Search consumer suite + Ghost `examples/` + `TestAC_*` for issue, spell, quest,
Same oracle → same test (extend / table-drive). Same setup, different oracle → helper or `t.Run`, not a second login. NEVER copy Ghost `examples/` without changing the oracle. One tracked issue → one primary `TestAC_<id>_…`; extra edges as `t.Run` or siblings only if isolation requires. Same root cause + same assertion surface → merge.
Inventory (`e2e/README.md` + suite comments): category, one-sentence oracle, P0–P3, `covered` (test name) / `gap` / `blocked-harness` / `manual-only`, issue links, note. MUST update when adding coverage or finding a gap. `blocked-harness` MUST name the missing API (opcode, waiter, multi-realm, …). Closing a P0/P1 gap is preferred when touching that subsystem. When pruning, merge redundant tests and refresh the inventory.
Inventory (`e2e/README.md` + suite comments): category, one-sentence oracle, P0–P3, `covered` (test name) / `gap` / `blocked-harness` / `manual-only`, issue links, note. Update it when the user asked you to add or remove coverage. `blocked-harness` MUST name the missing API (opcode, waiter, multi-realm, …). When pruning, merge redundant tests and refresh the inventory.
Prefer one solid test per merged player-visible bugfix. Deepen critical categories over new weak ones. Every test: unique oracle + owner (issue or feature). Delete or merge tests that no longer map to a behaviour.
Do not add a test per bugfix. Every existing test: unique oracle + owner (issue or feature). Delete or merge tests that no longer map to a behaviour.
## Priority (next test)
## Priority (only if the user asked for a new test)
Highest feasible MVT:
@@ -144,7 +149,7 @@ Highest feasible MVT:
| **P2** | Single-boss timers/waves/targeting; guild charter/bank; multi-bot PvP; realm-scoped GM / persisted visibility |
| **P3** | QoL, display, nondeterministic farm; full raid clears (out of default growth) |
On a PR: implement e2e for the highest-priority trigger the PR activates. Do not add P3 in the same change unless asked.
On a PR: do not add e2e unless the user asked. If they did, pick the highest-priority trigger they named. Do not add P3 in the same change unless asked.
## Isolation, comments, flakes
@@ -167,6 +172,6 @@ NEVER let a test PASS while the product oracle is wrong. NEVER multi-retry + sof
Update tests in the same PR as the behaviour change. Harness renames: update consumers in the same landing window. Ghost `e2e/examples/` are patterns; AC regressions live in the consumer suite.
Scratch MUST be `e2e/local/` (gitignored except `local/README.md`). NEVER commit throwaways. Promote into `e2e/suites/` next to related tests in the same PR as the fix. Prefer live e2e over ad-hoc GM when the stack is up.
Scratch MUST be `e2e/local/` (gitignored except `local/README.md`). NEVER commit throwaways. Do not promote scratch into `e2e/suites/` unless the user asked.
Local (from `e2e/`): `go test -tags=e2e ./...`. Official-repo PR and master CI: full suite after nopch clang-18 (reuses those binaries). Dispatch `-f scope=smoke` for a smaller run. Touch `.github/workflows/e2e-live.yml` only when changing CI.
+19 -3
View File
@@ -54,7 +54,7 @@ on:
required: false
default: '1'
timeout:
description: 'go test -timeout (smoke default 30m; full should be >=120m)'
description: 'go test -timeout (smoke default 30m; full default 60m; job ceiling 90m)'
required: false
default: ''
parallel:
@@ -86,11 +86,16 @@ env:
jobs:
e2e:
name: e2e (native / ${{ inputs.scope || 'full' }})
# github.repository is the base repo on pull_request, so fork PRs still run here.
# This only skips the job inside a fork's own CI.
if: github.repository == 'azerothcore/azerothcore-wotlk'
# Artifact path (PR nopch): ubuntu-24.04 to match the uploaded binaries.
# Dispatch (no artifact): optional self-hosted via E2E_RUNS_ON.
# Ephemeral runners only: MySQL grants persist accounts, and Stop servers
# pkill env/dist/bin/worldserver and authserver.
runs-on: ${{ inputs.artifact_run_id != '' && 'ubuntu-24.04' || vars.E2E_RUNS_ON || 'ubuntu-24.04' }}
timeout-minutes: 480
# Real ceiling. go test -timeout is per package (full default 60m).
timeout-minutes: 90
steps:
- name: Free disk
run: |
@@ -108,6 +113,17 @@ jobs:
go-version-file: e2e/go.mod
cache-dependency-path: e2e/go.sum
- name: Check gofmt
working-directory: e2e
run: |
set -euo pipefail
dirty="$(gofmt -l .)"
if [[ -n "${dirty}" ]]; then
echo "::error::gofmt needed"
printf '%s\n' "${dirty}"
exit 1
fi
# AzerothGhost is a public Go module (e2e/go.mod; repo azerothcore/AzerothGhost).
- name: Download e2e modules
working-directory: e2e
@@ -360,7 +376,7 @@ jobs:
|| { echo "bad timeout duration: ${E2E_TIMEOUT_INPUT}"; exit 1; }
timeout="${E2E_TIMEOUT_INPUT}"
elif [[ "${scope}" == "full" ]]; then
timeout="180m"
timeout="60m"
else
timeout="30m"
fi
+3 -4
View File
@@ -7,8 +7,7 @@ AzerothCore is a C++ MMORPG server emulator for World of Warcraft 3.3.5a (WotLK)
- **Do not configure or build unless explicitly asked.** Builds are slow and rarely needed for code changes.
- **Never edit SQL files outside `data/sql/updates/pending_db_*/` unless explicitly requested.** `data/sql/base/`, `data/sql/archive/`, and `data/sql/updates/db_*/` are immutable.
- Formatting follows `.editorconfig`: UTF-8, LF, max 120 cols, trailing newline, no trailing whitespace; 4-space indent for C++ (tabs forbidden), 2-space for JSON/YAML/sh/ts/js.
- **Prefer live-stack e2e to debug/validate player-visible behaviour** when a local auth+world+MySQL stack is available (protocol, combat, quests, loot, death, multi-bot). See `e2e/README.md` and AzerothGhost `e2e/LLM_GUIDE.md`. Do not invent e2e for pure unit-sized logic — see `.agents/docs/e2e-policy.md`.
- **Scratch e2e only under `e2e/local/`** (gitignored). Never commit throwaway debug tests. Promote keepers into `e2e/suites/` or `e2e/smoke/`.
- **Do not write or add live-stack e2e tests unless the user explicitly asks.** Do not create `e2e/local/` scratch tests to validate a fix. Existing `e2e/` tests that break after a change may be updated. Do not mention missing coverage unless the user asked about it.
- Planning docs go in `.agents/plans/<task-slug>/` (gitignored), named `<task-slug>.<TYPE>.md` (`PLAN`, `REQUIREMENTS`, `ANALYSIS`, …).
- **Credit upstream authors.** Code, a mechanism, or data mirrored from another core (TrinityCore, cMaNGOS, …) is committed with `--author` naming the original commit's author (extra sources as `Co-authored-by`), even when rewritten against AC or confirmed by own sniffs; find them in the upstream file's commit history.
@@ -24,7 +23,7 @@ Read the matching doc(s) BEFORE starting the task:
- Reviewing a changeset or PR → `.agents/docs/code-review.md`
- Self-reviewing, or opening or updating a PR → also `.agents/docs/self-review-rules.md`
- Touching a subsystem that has a doc in `.agents/docs/systems/` → read that doc too
- Writing, debugging, or changing live-stack e2e (`e2e/`) → `e2e/README.md`, `.agents/docs/e2e-policy.md`, and AzerothGhost `e2e/LLM_GUIDE.md` (scratch work → `e2e/local/`)
- Changing existing live-stack e2e (`e2e/`), and only when the user asked or an existing test broke → `e2e/README.md` and `.agents/docs/e2e-policy.md`
- Capturing a lesson or adding/updating agent docs → `.agents/docs/README.md`
## Repository layout
@@ -36,7 +35,7 @@ Read the matching doc(s) BEFORE starting the task:
- `src/server/shared/` — code shared by auth and world servers.
- `src/server/apps/{authserver,worldserver}/` — entry points (ports 3724 and 8085).
- `src/test/` — unit tests + mocks.
- `e2e/` — live-stack Go e2e (AzerothGhost harness); see `e2e/README.md`. Scratch/debug tests: `e2e/local/` (gitignored).
- `e2e/` — live-stack Go e2e (AzerothGhost harness); see `e2e/README.md`.
- `data/sql/` — `base/` (historical schema), `updates/db_*/` (merged), `updates/pending_db_*/` (in-flight), `custom/` (gitignored).
- `modules/` — external modules (see below).
- `apps/` — helper scripts; `apps/codestyle/` holds the lint scripts.
+10 -11
View File
@@ -6,13 +6,13 @@ and run against a **live** authserver + worldserver + MySQL.
Offline `go test ./...` (without `-tags=e2e`) skips these packages.
Authoring rules for new tests live in the harness:
Authoring rules for the existing suite live in the harness:
| Doc | Audience |
|-----|----------|
| [LLM_GUIDE.md](https://github.com/azerothcore/AzerothGhost/blob/v1.0.8/e2e/LLM_GUIDE.md) | Compact MUST/NEVER + APIs (LLMs and humans) |
| [LLM_GUIDE.md](https://github.com/azerothcore/AzerothGhost/blob/v1.0.8/e2e/LLM_GUIDE.md) | Compact MUST/NEVER + APIs |
| [EXAMPLES.md](https://github.com/azerothcore/AzerothGhost/blob/v1.0.8/e2e/EXAMPLES.md) | Full recipes and skeletons |
| `.agents/docs/e2e-policy.md` | When to add e2e vs unit tests (agent/review policy) |
| `.agents/docs/e2e-policy.md` | Do not add e2e unless asked; suite conventions |
---
@@ -127,19 +127,16 @@ go test -tags=e2e ./suites/... -run TestPets_SummonWaitDismiss -count=1 -v -time
Every live test uses `//go:build e2e` and should call `meta.Begin(t, meta.TestMeta{…})` before expensive setup.
### Scratch / agent debug (`local/`)
### Scratch (`local/`)
When validating a fix on a **live** stack (player-visible combat, protocol, quests, multi-bot), prefer writing a small e2e under **`e2e/local/`** instead of ad-hoc GM spam or long manual checklists. That tree is **not committed**.
`e2e/local/` is gitignored except `local/README.md`. Do not add tests there unless asked. If something is already there:
```bash
# create e.g. local/repro/repro_e2e_test.go (//go:build e2e)
make e2e-local
# or:
go test -tags=e2e ./local/... -count=1 -v -timeout 30m -parallel 1
```
If the scenario should stay as a regression, **move** it into `suites/` next to related tests with proper `meta.Begin` tags — see `.agents/docs/e2e-policy.md`.
### Inventory
| Category | Oracle | Pri | Coverage | Issue |
@@ -349,9 +346,11 @@ A test that fails intermittently on a **correct** core is a test/harness bug unt
---
## Policy (when to add e2e)
## Policy
Use `.agents/docs/e2e-policy.md` for decision trees (e2e vs unit, mandatory triggers, MVT). This README is **how to run and structure** the suite; the harness guides are **how to author** scenarios.
Do not add e2e tests unless asked. Do not mention missing coverage unless the user asked about it.
`.agents/docs/e2e-policy.md` is suite convention when changing existing tests. This README is
**how to run and structure** the suite.
---
@@ -367,6 +366,6 @@ Details live in the workflow files only:
| Merge to `master` | same clang-18 nopch build, then **full** e2e again (flake + merge-base drift) |
| Actions → **e2e-live** → Run workflow (official repo; needs workflow on default branch, or `gh workflow run … --ref e2e`) | Compiles on the runner; choose scope (smoke/full) |
Day-to-day development and agent debugging should use a **local** stack + `e2e/local/` or the committed suites — not CI setup docs.
Day-to-day development should use a **local** stack and the committed suites, not CI setup docs.
Greppable failure prefixes: `precondition:`, `AC#N CONFIRMED BUG:`, `harness:`, `WARNING:`.
+2 -45
View File
@@ -1,15 +1,7 @@
# e2e/local — scratch / debug tests (not committed)
Put **throwaway** live-stack tests here while debugging a core/script change.
Everything under this directory is **gitignored** except this README.
## When to use
| Put it here | Promote to |
|-------------|------------|
| One-off repro while fixing a bug | `suites/…` or `suites/issues/` once it should stay green |
| Agent exploratory scenario | Same — only keep if policy says e2e is warranted |
| Experiments with harness APIs | Drop or move when done |
Do not add tests here unless asked. Do not promote scratch into `smoke/` or `suites/` unless asked.
Do **not** put permanent regressions here; CI and reviewers only look at `smoke/` and `suites/`.
@@ -21,43 +13,8 @@ From `e2e/` with stack up and `E2E_*` set (see parent [README.md](../README.md))
# all scratch packages
go test -tags=e2e ./local/... -count=1 -v -timeout 30m -parallel 1
# one file’s package (create local/foo/foo_e2e_test.go)
# one existing package
go test -tags=e2e ./local/foo -count=1 -v -timeout 15m -parallel 1
```
From the repository root: `make -C e2e e2e-local`. From `e2e/`: `make e2e-local`.
## Minimal skeleton
```go
//go:build e2e
package local_debug_test
import (
"testing"
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
)
func TestLocal_Scratch(t *testing.T) {
meta.Begin(t, meta.TestMeta{
Tags: []string{"local", "short"},
Runtime: "short",
Category: "local",
})
bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{
Prefix: "Loc",
Class: e2eharness.ClassWarrior,
Level: 80,
})
bot.TeleportPad(t, e2eharness.PackagePad(t))
// … drive and assert …
t.Logf("PASS local scratch")
}
```
Authoring rules: AzerothGhost `e2e/LLM_GUIDE.md`. When to keep e2e long-term: `.agents/docs/e2e-policy.md`.
+1 -1
View File
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// SMOKE-07 first: fresh-account create (run early — late runs saw flaky auth EOF
+1 -1
View File
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// Battle Shout ranks (player-cast, cancelable). Do not use GM .aura / 45614 Blending In
+1 -1
View File
@@ -8,9 +8,9 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/client"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
const (
+1 -1
View File
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
const spellSummonImp = 688
+1 -1
View File
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// pullDummy is a high-HP training dummy for kill/engage-without-target oracles.
+44 -23
View File
@@ -8,32 +8,56 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// VEH-* : EnterVehicle / ExitVehicle / IsOnVehicle via Stormwind Steed on PackagePad.
// VEH-* : EnterVehicle / ExitVehicle / IsOnVehicle via Stormwind Steed.
// Armistice (64373) is required by creature_template vehicle conditions.
// spell_area only keeps that aura on the Argent Tournament grounds; PackagePad
// (Outland) applies it then strips it, and VehicleAI ejects the rider.
// spellArmistice (64373) is required by creature_template vehicle conditions
// on Stormwind Steed 33217. VehicleAI/SmartAI ExitVehicle the rider without it
// (conditions.comment: "Vehicle Stormwind Steed requires aura Armistice").
const spellArmistice uint32 = 64373
func spawnAndBoardSteed(t *testing.T, bot *e2eharness.ScenarioBot) (vehGUID, boarded uint64) {
func placeAtArgentTournament(t *testing.T, bots ...*e2eharness.ScenarioBot) {
t.Helper()
bot.ApplyAura(t, spellArmistice)
if !bot.HasAura(spellArmistice) {
e2eharness.Preconditionf(t, "Armistice %d not on player (steed vehicle condition)", spellArmistice)
for i, bot := range bots {
if i == 0 {
bot.TeleNamed(t, "ArgentTournament")
continue
}
x, y, z, m := bots[0].Pos()
bot.Teleport(t, x+1.5*float32(i), y, z, m)
}
vehGUID = bot.Spawn(t, e2eharness.CreatureStormwindSteed, 15*time.Second)
}
func ensureArmistice(t *testing.T, bot *e2eharness.ScenarioBot) {
t.Helper()
// spell_area autocast only. Do not GM-apply: that would hide an area regression.
deadline := time.Now().Add(8 * time.Second)
for time.Now().Before(deadline) {
if bot.HasAura(spellArmistice) {
return
}
time.Sleep(50 * time.Millisecond)
}
e2eharness.Preconditionf(t, "Armistice %d missing after Argent Tournament enter (spell_area)", spellArmistice)
}
func spawnAndBoardSteed(t *testing.T, bot *e2eharness.ScenarioBot) uint64 {
t.Helper()
ensureArmistice(t, bot)
vehGUID := bot.Spawn(t, e2eharness.CreatureStormwindSteed, 15*time.Second)
if vehGUID == 0 {
e2eharness.Preconditionf(t, "Stormwind Steed spawn returned guid 0")
}
boarded = bot.EnterVehicle(t, vehGUID, 12*time.Second)
// Re-check immediately before click: spell_area can strip a stale apply.
ensureArmistice(t, bot)
bot.EnterVehicle(t, vehGUID, 12*time.Second)
if !bot.IsOnVehicle() {
e2eharness.Preconditionf(t, "not on steed 0x%X after enter (charm=0x%X)", vehGUID, bot.World.PlayerCharmGUID())
}
return vehGUID, boarded
return vehGUID
}
// VEH-01: two bots at the pad; one boards a Stormwind Steed.
@@ -41,9 +65,8 @@ func TestVehicles_MultiBotColocated(t *testing.T) {
meta.Begin(t, meta.TestMeta{Tags: []string{"short", "combat", "multi_bot"}, Runtime: "short", Category: "combat/vehicles"})
bots := e2eharness.NewScenario(t, e2eharness.ScenarioOpts{Prefix: "VehCol", Count: 2, Level: 80})
pad := e2eharness.PackagePad(t)
e2eharness.TeleportAllPad(t, bots, pad)
vehGUID, _ := spawnAndBoardSteed(t, bots[0])
placeAtArgentTournament(t, bots...)
vehGUID := spawnAndBoardSteed(t, bots[0])
bots[1].AssertWorldAlive(t)
t.Logf("PASS vehicle multi-bot colocation boarded=0x%X", vehGUID)
}
@@ -53,10 +76,10 @@ func TestVehicles_RelogAfterTeleBaseline(t *testing.T) {
meta.Begin(t, meta.TestMeta{Tags: []string{"short", "combat", "protocol"}, Runtime: "short", Category: "combat/vehicles"})
bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{Prefix: "VehRl", Level: 80})
bot.TeleportPad(t, e2eharness.PackagePad(t))
placeAtArgentTournament(t, bot)
bot.Save(t)
bot.Relog(t)
vehGUID, _ := spawnAndBoardSteed(t, bot)
vehGUID := spawnAndBoardSteed(t, bot)
t.Logf("PASS relog then board steed 0x%X", vehGUID)
}
@@ -66,8 +89,8 @@ func TestVehicles_HardDisconnectWorldAlive(t *testing.T) {
probe := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{Prefix: "VehPr", Level: 10})
vic := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{Prefix: "VehVc", Level: 80})
vic.TeleportPad(t, e2eharness.PackagePad(t))
vehGUID, _ := spawnAndBoardSteed(t, vic)
placeAtArgentTournament(t, vic)
vehGUID := spawnAndBoardSteed(t, vic)
e2eharness.HardDisconnectAndProbe(t, vic, probe, 0)
t.Logf("PASS passenger hard-drop world alive steed=0x%X", vehGUID)
}
@@ -77,11 +100,11 @@ func TestVehicles_EnterExitStormwindSteed(t *testing.T) {
meta.Begin(t, meta.TestMeta{Tags: []string{"short", "combat", "serial"}, Runtime: "short", Category: "combat/vehicles"})
bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{Prefix: "VehEn", Level: 80})
bot.TeleportPad(t, e2eharness.PackagePad(t))
placeAtArgentTournament(t, bot)
if bot.IsOnVehicle() {
e2eharness.Preconditionf(t, "already on vehicle before enter")
}
vehGUID, _ := spawnAndBoardSteed(t, bot)
vehGUID := spawnAndBoardSteed(t, bot)
bot.ExitVehicle(t, 12*time.Second)
if bot.IsOnVehicle() {
e2eharness.Assertf(t, "still IsOnVehicle after ExitVehicle (0x%X)", bot.VehicleGUID())
@@ -89,5 +112,3 @@ func TestVehicles_EnterExitStormwindSteed(t *testing.T) {
bot.AssertWorldAlive(t)
t.Logf("PASS EnterVehicle/ExitVehicle Stormwind Steed 0x%X", vehGUID)
}
@@ -9,8 +9,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// GUILD-01: charter buy + full turn-in via harness CreateGuildViaCharter (SetupGuildLeader).
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// BIND-* : instance party baselines + ritual summon (3 roles) + #10708.
@@ -248,5 +248,3 @@ func TestBind_CharacterInstanceAfterEnter(t *testing.T) {
bot.AssertWorldAlive(t)
t.Logf("PASS character_instance rows=%d map=%d", n, m)
}
@@ -125,11 +125,24 @@ func TestAC_26363_TimmyEmergesAfterSquareCleared(t *testing.T) {
// Center of the 15 relevant Scarlet spawns in Crusaders' Square. The 55-yard
// observation radius excludes other Scarlet spawns with the same entries.
bot.Teleport(t, 3660, -3180, 127, stratholmeMap)
bot.CombatReady(t)
triggers := waitForTimmyActivationSet(t, bot, 20*time.Second)
if len(triggers) != 15 {
e2eharness.Preconditionf(t, "loaded Timmy activation area has %d relevant Scarlets, want 15", len(triggers))
}
bot.CombatReady(t)
// Timmy's emerge tick can fire before this client's cache has the Square
// pack (same SHA on ephemeral CI flips pass / "already present"). One
// kill+.respawn after the pack is visible resets his SAI.
if leftover := bot.FindUnit(npcTimmyTheCruel, 100); leftover != 0 {
t.Logf("Timmy 0x%X in cache with Square loaded, killing + .respawn", leftover)
bot.DamageKill(t, []uint64{leftover}, 10_000_000, 10*time.Second)
bot.GM(t, ".respawn")
bot.FlushWorld(t)
triggers = waitForTimmyActivationSet(t, bot, 20*time.Second)
if len(triggers) != 15 {
e2eharness.Preconditionf(t, "after reset, activation area has %d relevant Scarlets, want 15", len(triggers))
}
}
if timmy := bot.FindUnit(npcTimmyTheCruel, 100); timmy != 0 {
e2eharness.ConfirmedBugf(t, 26363, "Timmy was already present as 0x%X before the Scarlet activation area was cleared", timmy)
}
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// OPEN(e2e): re-enable when AC#26266 is fixed — Charge near Kologarn must not drop below bridge Z.
@@ -75,6 +75,7 @@ func TestAC_27095_FreyaAlliesSpawnRateReduction(t *testing.T) {
Prefix: "Freya",
Level: 80,
})
// Stay GM through the raid enter (.go xyz onto 603 is ignored after .gm off).
const (
npcFreya10 = uint32(32906)
@@ -100,16 +101,39 @@ func TestAC_27095_FreyaAlliesSpawnRateReduction(t *testing.T) {
return "Unknown"
}
bot.TeleNamed(t, "Freya")
// Raid interior pad (game_tele BossFreya). .go xyz is reliable; a missing
// custom "Freya" name hangs TeleNamed for 60s.
bot.Teleport(t, 2326.82, -48.131, 424.963, e2eharness.MapUlduar)
if _, _, _, m := bot.Pos(); m != e2eharness.MapUlduar {
e2eharness.Preconditionf(t, "not in Ulduar after Freya pad tele map=%d", m)
}
bot.GoCreatureID(t, npcFreya10)
// Now drop GM. FlushWorld/.gps beside Freya can evade her out of cache,
// so re-acquire a living GUID before Engage/FaceUnit.
bot.CombatReady(t)
freyaGUID := bot.WaitUnitAny(t, 30*time.Second, npcFreya10, npcFreya25)
// Evade can leave a 0 HP object in cache. Re-poll until we have a living
// Freya, not only when the GUID disappears.
var freyaGUID uint64
deadline := time.Now().Add(30 * time.Second)
for {
freyaGUID = bot.WaitUnitAny(t, 10*time.Second, npcFreya10, npcFreya25)
if hp, maxHP := bot.UnitHP(freyaGUID); maxHP > 0 && hp > 0 {
if bot.World.GetObject(freyaGUID) != nil {
break
}
}
if !time.Now().Before(deadline) {
e2eharness.Preconditionf(t, "no living Freya in cache after GoCreatureID (last=0x%X)", freyaGUID)
}
time.Sleep(50 * time.Millisecond)
}
bot.Engage(t, freyaGUID, 15*time.Second)
tr := e2eharness.NewSpawnSetTracker(allyEntries, 3*time.Second)
tr.KindOf = func(entry uint32) string { return label(entry) }
sets := tr.WaitSets(t, bot.World, 2, 4*time.Minute)
// Waves are ~60s apart. 90s covers two waves; 4m just delayed a miss.
sets := tr.WaitSets(t, bot.World, 2, 90*time.Second)
t.Logf("Set1=%s units=%d Set2=%s units=%d gap=%s",
sets[0].Kind, len(sets[0].Guids),
sets[1].Kind, len(sets[1].Guids),
@@ -118,7 +142,7 @@ func TestAC_27095_FreyaAlliesSpawnRateReduction(t *testing.T) {
// Detonating Lashers explode on death — if Set1 is Lashers, wait for Set3
// and kill Set2 (still: older set while a newer set is up).
if sets[0].Kind == "Lashers" {
sets = tr.WaitSets(t, bot.World, 3, 4*time.Minute)
sets = tr.WaitSets(t, bot.World, 3, 150*time.Second)
}
var older, newer e2eharness.SpawnSet
@@ -131,7 +155,6 @@ func TestAC_27095_FreyaAlliesSpawnRateReduction(t *testing.T) {
e2eharness.Preconditionf(t, "cannot find a non-Lasher older set to kill without collateral explosions")
}
time.Sleep(2 * time.Second)
tr.Poll(bot.World, time.Now())
var olderLive []uint64
@@ -176,9 +199,9 @@ func TestAC_27095_FreyaAlliesSpawnRateReduction(t *testing.T) {
for _, s := range bot.UnitsByEntry(120, allyEntries...) {
knownAtKill[s.GUID] = struct{}{}
}
fresh := bot.WaitNewUnits(t, knownAtKill, allyEntries, 90*time.Second)
fresh := bot.WaitNewUnits(t, knownAtKill, allyEntries, 75*time.Second)
if len(fresh) == 0 {
e2eharness.Preconditionf(t, "no new ally set spawned within 90s after older-set kill")
e2eharness.Preconditionf(t, "no new ally set spawned within 75s after older-set kill")
}
nextT := time.Now()
fromNewer := nextT.Sub(newer.SpawnT)
@@ -245,7 +268,7 @@ func TestUlduar_MultiBotLoginNearBossPad(t *testing.T) {
Level: 80,
})
for _, b := range bots {
b.TeleNamed(t, "Freya")
b.Teleport(t, 2326.82, -48.131, 424.963, e2eharness.MapUlduar)
}
_, _, _, m0 := bots[0].Pos()
_, _, _, m1 := bots[1].Pos()
+1 -1
View File
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// EQUIP-01: add+inventory oracle (EquipEntry is EQUIP-04).
@@ -7,8 +7,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// SESS-01: position readable after login (seed of inventory/pos load).
@@ -9,8 +9,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// TELE-01: cross-map teleport (EK → Northrend).
+1 -1
View File
@@ -9,8 +9,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// OPEN(e2e): replace this package with a real escort quest fixture (start → follow →
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// QLIFE-03 / #26549: STAY_ALIVE quest fails on death.
+1 -1
View File
@@ -8,9 +8,9 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/client"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// GRP-01: FormParty → both InGroup, leader flag.
+1 -1
View File
@@ -8,9 +8,9 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/client"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// LOOT-01: Need vs Greed on a guaranteed Uncommon drop (Crimson Templar).
+15 -11
View File
@@ -9,9 +9,9 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/client"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// Cheap stackable trade bait (Linen Cloth).
@@ -108,6 +108,15 @@ func waitInvAtLeast(t *testing.T, bot *e2eharness.ScenarioBot, entry uint32, lea
return n
}
func waitInvEqual(t *testing.T, bot *e2eharness.ScenarioBot, entry uint32, want int, timeout time.Duration) int {
t.Helper()
n := waitInv(t, bot, entry, func(got int) bool { return got == want }, timeout)
if n != want {
e2eharness.Assertf(t, "inventory entry=%d count=%d want=%d", entry, n, want)
}
return n
}
// Spec 1 — Item + gold dual-accept.
func TestTrade_ItemGoldDualAcceptInventories(t *testing.T) {
meta.Begin(t, meta.TestMeta{Tags: []string{"short", "trade", "multi_bot"}, Runtime: "short", Category: "social/trade"})
@@ -233,24 +242,19 @@ func TestTrade_StackableMerge(t *testing.T) {
// After accept: totals conserved and B gains.
bag, slot := a.AddItemWait(t, itemLinenCloth, 5)
b.AddItemWait(t, itemLinenCloth, 3)
a0 := a.InventoryCount(t, itemLinenCloth)
b0 := b.InventoryCount(t, itemLinenCloth)
if a0 < 5 || b0 < 3 {
e2eharness.Preconditionf(t, "seed failed a=%d b=%d", a0, b0)
}
// Same oracle as the dual-accept test: client push can land before CharDB.
a0 := waitInvAtLeast(t, a, itemLinenCloth, 5, 10*time.Second)
b0 := waitInvAtLeast(t, b, itemLinenCloth, 3, 10*time.Second)
e2eharness.OpenTrade(t, a, b)
a.SetTradeItem(t, 0, bag, slot)
e2eharness.CompleteTrade(t, a, b)
a1 := a.InventoryCount(t, itemLinenCloth)
b1 := b.InventoryCount(t, itemLinenCloth)
a1 := waitInvEqual(t, a, itemLinenCloth, a0-5, 10*time.Second)
b1 := waitInvEqual(t, b, itemLinenCloth, b0+5, 10*time.Second)
if a1+b1 != a0+b0 {
e2eharness.Assertf(t, "linen not conserved %d+%d → %d+%d", a0, b0, a1, b1)
}
if b1 <= b0 {
e2eharness.Assertf(t, "B did not gain stack %d→%d", b0, b1)
}
t.Logf("PASS stackable merge/conserve a %d→%d b %d→%d", a0, a1, b0, b1)
}
+1 -1
View File
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// Issue: https://github.com/azerothcore/azerothcore-wotlk/issues/26130
+1 -1
View File
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// CAST-01: live cast pipeline — Charge succeeds on spawned dummy after CombatReady.
@@ -8,8 +8,8 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
)
// OPEN(e2e): re-enable when AC#26774 is fixed