Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ jobs:

- store_test_results:
path: /tmp/gameplay-tests-results
# One folder per game, holding its results, GDevelop's output and the
# screenshots its tests took (in `gameplay-test-screenshots/`, which the
# results file points at with a relative path).
- store_artifacts:
path: /tmp/gameplay-tests-artifacts
destination: gameplay-tests
Expand Down
995 changes: 0 additions & 995 deletions GAMEPLAY_TESTS_FEEDBACK-starters.md

This file was deleted.

6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ from one CI container to the next. So a game whose run failed **only** with
wall-clock timeouts is run a second time, and the second run is the one that
counts. A failed assertion is never retried — that is a real result.

The CI keeps one folder of artifacts per game (under `gameplay-tests/`),
holding its `results.json`, GDevelop's output, and the screenshots its tests
took with `harness.takeScreenshot(...)` in a `gameplay-test-screenshots/`
folder next to the results — which refer to them by a relative path, so a
downloaded results file still points at the right images.

The tests of every game can also be run on a branch, without waiting for it to
land on `main`, by triggering a CircleCI pipeline with the
`run-all-gameplay-tests` parameter set to `true`. The number of parallel
Expand Down
5 changes: 2 additions & 3 deletions examples/starting-3d-car-racing/starting-3d-car-racing.json
Original file line number Diff line number Diff line change
Expand Up @@ -2139,11 +2139,10 @@
"harness.watch('PlayerCar');",
"",
"const getCar = () => harness.getObjects('PlayerCar')[0];",
"/** Read one of the object variables the game keeps the race progress in. */",
"/** The race progress the game keeps in the car's own variables. */",
"const readProgress = () => {",
" const variables = getCar().variables;",
" const read = (name) => {",
" const variable = variables.find((one) => one.name === name);",
" const variable = harness.getObjectVariable('PlayerCar', name);",
" return variable ? Number(variable.value) : null;",
" };",
" return { lap: read('LapNumber'), checkpoint: read('LastCheckPoint') };",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1233,45 +1233,6 @@
"const getPieceById = (name, id) =>",
" harness.getObjects(name).find((one) => one.id === id);",
"",
"/**",
" * Put the mouse cursor on a point of the board.",
" *",
" * `setMousePosition` cannot be used here: the board is drawn by a 3D camera,",
" * and the scene-to-screen conversion it uses ignores 3D rotations, so the",
" * cursor lands somewhere else than asked. The screen-to-scene conversion of",
" * the layer does handle them (it is the one the game itself reads the cursor",
" * with), so the screen position is searched for instead: start at the middle",
" * of the screen and step toward the wanted point, following the conversion's",
" * own slope. It converges in a handful of iterations and costs no frame.",
" */",
"const layer = harness.getRuntimeLayer('');",
"const sceneAt = (screenX, screenY) => {",
" const point = layer.convertCoords(screenX, screenY, 0, [0, 0]);",
" return { x: point[0], y: point[1] };",
"};",
"const pointMouseAt = (sceneX, sceneY) => {",
" let screenX = harness.getGameResolutionWidth() / 2;",
" let screenY = harness.getGameResolutionHeight() / 2;",
" for (let iteration = 0; iteration < 15; iteration++) {",
" const here = sceneAt(screenX, screenY);",
" const errorX = sceneX - here.x;",
" const errorY = sceneY - here.y;",
" if (Math.hypot(errorX, errorY) < 0.5) break;",
" const step = 4;",
" const right = sceneAt(screenX + step, screenY);",
" const down = sceneAt(screenX, screenY + step);",
" const a = (right.x - here.x) / step;",
" const b = (down.x - here.x) / step;",
" const c = (right.y - here.y) / step;",
" const d = (down.y - here.y) / step;",
" const determinant = a * d - b * c;",
" if (!determinant) break;",
" screenX += (d * errorX - b * errorY) / determinant;",
" screenY += (-c * errorX + a * errorY) / determinant;",
" }",
" harness.setMousePositionScreen(screenX, screenY);",
"};",
"",
"await harness.stepFrames(4);",
"const piece = harness.getObjects('Tree')[0];",
"harness.assert(!!piece, 'There is a piece to drag on the board.');",
Expand Down Expand Up @@ -1311,15 +1272,16 @@
" // Where the cursor must end up for the piece's origin to land there.",
" const dropX = destinationX + (grabX - dragged.x);",
" const dropY = destinationY + (grabY - dragged.y);",
" pointMouseAt(grabX, grabY);",
" harness.setMousePosition(grabX, grabY, dragged.layer);",
" await harness.stepFrames(2);",
" harness.setMouseButtonPressed(true);",
" await harness.stepFrames(2);",
" const STEPS = 6;",
" for (let step = 1; step <= STEPS; step++) {",
" pointMouseAt(",
" harness.setMousePosition(",
" grabX + ((dropX - grabX) * step) / STEPS,",
" grabY + ((dropY - grabY) * step) / STEPS",
" grabY + ((dropY - grabY) * step) / STEPS,",
" dragged.layer",
" );",
" await harness.stepFrames(1);",
" }",
Expand Down Expand Up @@ -1362,45 +1324,6 @@
"const getPieceById = (name, id) =>",
" harness.getObjects(name).find((one) => one.id === id);",
"",
"/**",
" * Put the mouse cursor on a point of the board.",
" *",
" * `setMousePosition` cannot be used here: the board is drawn by a 3D camera,",
" * and the scene-to-screen conversion it uses ignores 3D rotations, so the",
" * cursor lands somewhere else than asked. The screen-to-scene conversion of",
" * the layer does handle them (it is the one the game itself reads the cursor",
" * with), so the screen position is searched for instead: start at the middle",
" * of the screen and step toward the wanted point, following the conversion's",
" * own slope. It converges in a handful of iterations and costs no frame.",
" */",
"const layer = harness.getRuntimeLayer('');",
"const sceneAt = (screenX, screenY) => {",
" const point = layer.convertCoords(screenX, screenY, 0, [0, 0]);",
" return { x: point[0], y: point[1] };",
"};",
"const pointMouseAt = (sceneX, sceneY) => {",
" let screenX = harness.getGameResolutionWidth() / 2;",
" let screenY = harness.getGameResolutionHeight() / 2;",
" for (let iteration = 0; iteration < 15; iteration++) {",
" const here = sceneAt(screenX, screenY);",
" const errorX = sceneX - here.x;",
" const errorY = sceneY - here.y;",
" if (Math.hypot(errorX, errorY) < 0.5) break;",
" const step = 4;",
" const right = sceneAt(screenX + step, screenY);",
" const down = sceneAt(screenX, screenY + step);",
" const a = (right.x - here.x) / step;",
" const b = (down.x - here.x) / step;",
" const c = (right.y - here.y) / step;",
" const d = (down.y - here.y) / step;",
" const determinant = a * d - b * c;",
" if (!determinant) break;",
" screenX += (d * errorX - b * errorY) / determinant;",
" screenY += (-c * errorX + a * errorY) / determinant;",
" }",
" harness.setMousePositionScreen(screenX, screenY);",
"};",
"",
"await harness.stepFrames(4);",
"const piece = harness.getObjects('Tree')[0];",
"harness.assert(!!piece, 'There is a piece to drag on the board.');",
Expand All @@ -1425,15 +1348,16 @@
" // Where the cursor must end up for the piece's origin to land there.",
" const dropX = destinationX + (grabX - dragged.x);",
" const dropY = destinationY + (grabY - dragged.y);",
" pointMouseAt(grabX, grabY);",
" harness.setMousePosition(grabX, grabY, dragged.layer);",
" await harness.stepFrames(2);",
" harness.setMouseButtonPressed(true);",
" await harness.stepFrames(2);",
" const STEPS = 6;",
" for (let step = 1; step <= STEPS; step++) {",
" pointMouseAt(",
" harness.setMousePosition(",
" grabX + ((dropX - grabX) * step) / STEPS,",
" grabY + ((dropY - grabY) * step) / STEPS",
" grabY + ((dropY - grabY) * step) / STEPS,",
" dragged.layer",
" );",
" await harness.stepFrames(1);",
" }",
Expand Down
33 changes: 17 additions & 16 deletions examples/starting-3d-endless-runner/starting-3d-endless-runner.json
Original file line number Diff line number Diff line change
Expand Up @@ -2101,23 +2101,25 @@
" ' restartX=' + Math.round(restartX)",
");",
"",
"// Run (on its own) into it. The run ends when the player is put back where",
"// the scene starts it. Touching a hazard slows time down to 0.025 first, so",
"// the last part of this takes about twenty five frames during which almost",
"// nothing moves.",
"// Run (on its own) into it. The run ends when the game restarts the level,",
"// which the harness records as a `sceneReset`. Touching a hazard slows time",
"// down to 0.025 first, so the last part of this takes about twenty five",
"// frames during which almost nothing moves.",
"let furthestX = startX;",
"let hazardDistanceAtFurthestX = Infinity;",
"const restarted = await harness.stepUntil(",
" () => {",
" const currentX = getPlayer().centerX;",
" if (currentX > furthestX) {",
" furthestX = currentX;",
" const nearestHazard = harness.getNearby('Hazard', 'Player', 5000)[0];",
" if (nearestHazard) hazardDistanceAtFurthestX = nearestHazard.distance;",
" }",
" return furthestX > startX + 50 && currentX < startX - 20;",
" },",
" { maxFrames: 70 }",
" () => harness.getEventLog().some((event) => event.event === 'sceneReset'),",
" {",
" maxFrames: 70,",
" onFrame: () => {",
" const currentX = getPlayer().centerX;",
" if (currentX > furthestX) {",
" furthestX = currentX;",
" const nearestHazard = harness.getNearby('Hazard', 'Player', 5000)[0];",
" if (nearestHazard) hazardDistanceAtFurthestX = nearestHazard.distance;",
" }",
" },",
" }",
");",
"",
"console.log(",
Expand All @@ -2139,8 +2141,7 @@
");",
"harness.assert(",
" restarted,",
" 'Touching the hazard restarts the run: the player is back at the start of the level (it is at x=' +",
" Math.round(getPlayer().centerX) + ', it ran from x=' + Math.round(startX) + ').'",
" 'Touching the hazard restarts the run (the level was never restarted).'",
");"
]
}
Expand Down
Loading
Loading