Skip to content

Commit 034247b

Browse files
authored
Merge pull request #462 from wpengine/wpgraphql-logging-e2e-tests
test(logging): add end-to-end tests
2 parents 95f2396 + fdbff2f commit 034247b

File tree

14 files changed

+987
-4807
lines changed

14 files changed

+987
-4807
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ test-results/
1616
# WordPress
1717
.wp-env
1818
.wp-env.override.json
19-
wp-env/
2019
uploads/
2120
debug.log
2221
__MACOSX
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"plugins": [
3+
"https://github.com/wp-graphql/wp-graphql/releases/latest/download/wp-graphql.zip",
4+
"."
5+
],
6+
"env": {
7+
"tests": {
8+
"plugins": [
9+
"https://github.com/wp-graphql/wp-graphql/releases/latest/download/wp-graphql.zip",
10+
"https://github.com/johnbillion/wp-crontrol/archive/refs/tags/1.19.3.zip",
11+
".",
12+
"./tests/e2e/plugins/reset-wpgraphql-logging-settings/"
13+
]
14+
}
15+
},
16+
"config": {
17+
"WP_DEBUG": true
18+
},
19+
"mappings": {
20+
".htaccess": "./wp-env/setup/.htaccess"
21+
}
22+
}

plugins/wpgraphql-logging/TESTING.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ tests/
4545
├── _envs/ # Environment configs
4646
├── _output/ # Test output (logs, coverage)
4747
├── _support/ # Helper classes, modules
48+
├── e2e/ # End-to-end tests (Playwright)
49+
│ ├── specs/ # Test specifications
50+
│ │ ├── basic-usage.spec.js
51+
│ │ ├── data-cleanup.spec.js
52+
│ │ └── exclude-and-sanitize.spec.js
53+
│ ├── plugins/ # Test helper plugins
54+
│ ├── config/ # E2E test configuration
55+
│ ├── utils.js # Helper functions
56+
│ ├── constants.js # Test constants
57+
│ └── playwright.config.js # Playwright configuration
4858
├── wpunit/ # WPUnit (WordPress-aware unit/integration) test cases
4959
├── wpunit.suite.dist.yml
5060
└── wpunit/
@@ -95,6 +105,40 @@ Automated testing runs on every pull request via GitHub Actions for a modified p
95105
| **Codeception (WPUnit)** | Runs unit and integration tests | [View Workflow](../../actions/workflows/codeception.yml) |
96106

97107

108+
## E2E Tests
109+
110+
End-to-end tests use Playwright to simulate real user workflows from configuring the plugin to viewing logs and managing data.
111+
112+
### Test Suites
113+
114+
| Test Suite | Description | Key Scenarios |
115+
| -------------------------------- | -------------------------------- | -------------------------------------------------------- |
116+
| **basic-usage.spec.js** | Core logging functionality | Enable logging, execute queries, view logs, download CSV |
117+
| **exclude-and-sanitize.spec.js** | Query filtering and data privacy | Exclude queries, sanitize sensitive data |
118+
| **data-cleanup.spec.js** | Data management | Configure automatic log deletion, verify cron job |
119+
120+
### Test Helper Plugins
121+
122+
Located in `tests/e2e/plugins/`:
123+
124+
- **`reset-wpgraphql-logging-settings`** - Resets plugin settings and clears logs table for clean test state
125+
126+
### Running E2E Tests
127+
128+
```shell
129+
# Start wp-env (make sure Docker is running)
130+
npm run wp-env start
131+
132+
# Run all E2E tests
133+
npm run test:e2e
134+
135+
# Run specific test file
136+
npm run test:e2e tests/e2e/specs/basic-usage.spec.js
137+
138+
# Run tests in headed mode (with browser UI)
139+
npm run test:e2e:debug
140+
```
141+
98142
>[!IMPORTANT]
99143
> Test coverage for WP Unit Tests is **95%**. Any new code will require tests to be added in order to pass CI checks. This is set in [text](codeception.dist.yml) in the parameter `min_coverage`.
100144

plugins/wpgraphql-logging/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"author": "wpengine",
1515
"license": "GPL-2.0",
1616
"devDependencies": {
17-
"@playwright/test": "^1.52.0",
17+
"@playwright/test": "^1.56.1",
1818
"@wordpress/e2e-test-utils-playwright": "^1.25.0",
1919
"@wordpress/env": "^10.25.0",
2020
"@wordpress/jest-console": "^8.25.0",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { request } from "@playwright/test";
2+
import { RequestUtils } from "@wordpress/e2e-test-utils-playwright";
3+
4+
async function globalSetup(config) {
5+
const { baseURL, storageState } = config.projects[0].use;
6+
const storageStatePath =
7+
typeof storageState === "string" ? storageState : undefined;
8+
9+
const requestContext = await request.newContext({
10+
baseURL,
11+
});
12+
13+
const requestUtils = new RequestUtils(requestContext, {
14+
storageStatePath,
15+
});
16+
17+
// Authenticate and save the storageState to disk.
18+
await requestUtils.setupRest();
19+
20+
await Promise.all([requestUtils.resetPreferences()]);
21+
22+
await requestContext.dispose();
23+
}
24+
25+
export default globalSetup;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const PLUGIN_SLUG = "wpgraphql-logging";
2+
export const RESET_HELPER_PLUGIN_SLUG = "reset-wpgraphql-logging-settings";
3+
4+
export const GET_POSTS_QUERY = `
5+
query GetPosts {
6+
posts(first: 5) {
7+
nodes {
8+
id
9+
title
10+
date
11+
excerpt
12+
author {
13+
node {
14+
id
15+
name
16+
}
17+
}
18+
}
19+
}
20+
}
21+
`;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineConfig } from "@playwright/test";
2+
import baseConfig from "@wordpress/scripts/config/playwright.config";
3+
4+
const config = defineConfig({
5+
...baseConfig,
6+
globalSetup: require.resolve("./config/global-setup.js"),
7+
});
8+
9+
export default config;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
/**
3+
* Plugin Name: Reset WPGraphQL Logging settings
4+
* Description: This plugin resets WPGraphQL Logging settings on activation. It's only intended to be used for e2e testing purposes.
5+
*/
6+
7+
add_action('init', function () {
8+
if ($_SERVER['REQUEST_URI'] === '/wp-admin/options-general.php?page=wpgraphql-logging&reset=true') {
9+
global $wpdb;
10+
11+
// Reset settings
12+
update_option('wpgraphql_logging_settings', array());
13+
14+
// Clear logs table
15+
$table_name = $wpdb->prefix . 'wpgraphql_logging';
16+
$wpdb->query("TRUNCATE TABLE {$table_name}");
17+
}
18+
});
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { expect, test } from "@wordpress/e2e-test-utils-playwright";
2+
import {
3+
goToLoggingSettingsPage,
4+
goToLogsListPage,
5+
configureLogging,
6+
executeGraphQLQuery,
7+
resetPluginSettings,
8+
} from "../utils";
9+
import { GET_POSTS_QUERY } from "../constants";
10+
11+
test.describe("Basic Logging Usage", () => {
12+
test.beforeEach(async ({ admin, page }) => {
13+
await resetPluginSettings(admin);
14+
15+
// Go to settings page
16+
await goToLoggingSettingsPage(admin);
17+
await expect(page.locator("h1")).toHaveText("WPGraphQL Logging Settings");
18+
});
19+
20+
test("enables logging and logs GraphQL queries", async ({
21+
page,
22+
admin,
23+
request,
24+
}) => {
25+
await configureLogging(page, {
26+
enabled: true,
27+
dataSampling: "100",
28+
eventLogSelection: ["graphql_request_results"],
29+
});
30+
31+
const response = await executeGraphQLQuery(request, GET_POSTS_QUERY);
32+
expect(response.ok()).toBeTruthy();
33+
34+
// Check that the log appears in the logs list
35+
await goToLogsListPage(admin);
36+
await expect(page.locator("h1")).toContainText("WPGraphQL Logs");
37+
38+
const logRow = page
39+
.locator("#the-list tr")
40+
.filter({ hasText: "GetPosts" })
41+
.first();
42+
await expect(logRow).toBeVisible({ timeout: 10000 });
43+
44+
// View log details
45+
const viewLink = logRow.locator(".row-actions .view a");
46+
await expect(viewLink).toBeVisible();
47+
await viewLink.focus();
48+
await viewLink.click();
49+
50+
await expect(page.locator("h1")).toContainText("Log Entry");
51+
52+
const logTable = page.locator(".widefat.striped");
53+
await expect(logTable).toBeVisible();
54+
55+
const queryRow = logTable
56+
.locator("tr")
57+
.filter({ has: page.locator("th", { hasText: "Query" }) });
58+
await expect(queryRow).toBeVisible();
59+
await expect(queryRow.locator("td pre")).toContainText("query GetPosts");
60+
61+
// Go back to logs list
62+
const backLink = page
63+
.locator("p a.button")
64+
.filter({ hasText: "Back to Logs" });
65+
await expect(backLink).toBeVisible();
66+
67+
await backLink.click();
68+
await expect(page.locator("h1")).toContainText("WPGraphQL Logs");
69+
});
70+
71+
test("does not log when disabled", async ({ page, admin, request }) => {
72+
await configureLogging(page, {
73+
enabled: false,
74+
dataSampling: "100",
75+
});
76+
77+
// Make sure there are no logs
78+
await goToLogsListPage(admin);
79+
await expect(
80+
page.locator('td.colspanchange:has-text("No items found.")')
81+
).toBeVisible();
82+
83+
await executeGraphQLQuery(request, GET_POSTS_QUERY);
84+
85+
// Navigate to logs and verify no new logs were created
86+
await goToLogsListPage(admin);
87+
await expect(
88+
page.locator('td.colspanchange:has-text("No items found.")')
89+
).toBeVisible();
90+
});
91+
92+
test("downloads log as CSV with correct content", async ({
93+
page,
94+
admin,
95+
request,
96+
}) => {
97+
await configureLogging(page, {
98+
enabled: true,
99+
dataSampling: "100",
100+
eventLogSelection: ["graphql_request_results"],
101+
});
102+
103+
// Execute a GraphQL query
104+
const response = await executeGraphQLQuery(request, GET_POSTS_QUERY);
105+
expect(response.ok()).toBeTruthy();
106+
107+
// Check that the log appears in the logs list
108+
await goToLogsListPage(admin);
109+
await expect(page.locator("h1")).toContainText("WPGraphQL Logs");
110+
111+
const logRow = page
112+
.locator("#the-list tr")
113+
.filter({ hasText: "GetPosts" })
114+
.first();
115+
await expect(logRow).toBeVisible({ timeout: 10000 });
116+
117+
// View log details
118+
const downloadButton = logRow.locator(".row-actions .download a");
119+
await expect(downloadButton).toBeVisible();
120+
121+
const downloadPromise = page.waitForEvent("download");
122+
await downloadButton.focus();
123+
await downloadButton.click();
124+
const download = await downloadPromise;
125+
126+
// Verify download properties
127+
expect(download.suggestedFilename()).toMatch(/graphql_log_\d+\.csv/);
128+
expect(download.suggestedFilename()).toContain(".csv");
129+
130+
// Optionally save and verify the content
131+
const path = await download.path();
132+
const fs = require("fs");
133+
const content = fs.readFileSync(path, "utf8");
134+
135+
// Verify CSV contains expected data
136+
expect(content).toContain("ID");
137+
expect(content).toContain("Date");
138+
expect(content).toContain("Level");
139+
expect(content).toContain("Message");
140+
expect(content).toContain("GetPosts");
141+
});
142+
143+
test("should set data sampling to 10% and verify only 1 log is created", async ({
144+
page,
145+
admin,
146+
request,
147+
}) => {
148+
const QUERY_COUNT = 5;
149+
150+
await configureLogging(page, {
151+
enabled: true,
152+
dataSampling: "25",
153+
eventLogSelection: ["graphql_request_results"],
154+
});
155+
156+
// Execute a GraphQL queries
157+
const responses = await Promise.all(
158+
Array.from({ length: QUERY_COUNT }, async () =>
159+
executeGraphQLQuery(request, GET_POSTS_QUERY)
160+
)
161+
);
162+
await Promise.all(
163+
responses.map(async (response) => {
164+
return expect(response.ok()).toBeTruthy();
165+
})
166+
);
167+
168+
// Navigate to logs and verify no new logs were created
169+
await goToLogsListPage(admin);
170+
await expect(page.locator("h1")).toContainText("WPGraphQL Logs");
171+
172+
const logRow = page.locator("#the-list tr").filter({ hasText: "GetPosts" });
173+
174+
const logCount = await logRow.count();
175+
expect(logCount).toBeLessThan(QUERY_COUNT);
176+
});
177+
});

0 commit comments

Comments
 (0)