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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ test-results/
# WordPress
.wp-env
.wp-env.override.json
wp-env/
uploads/
debug.log
__MACOSX
Expand Down
22 changes: 22 additions & 0 deletions plugins/wpgraphql-logging/.wp-env.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"plugins": [
"https://github.com/wp-graphql/wp-graphql/releases/latest/download/wp-graphql.zip",
"."
],
"env": {
"tests": {
"plugins": [
"https://github.com/wp-graphql/wp-graphql/releases/latest/download/wp-graphql.zip",
"https://github.com/johnbillion/wp-crontrol/archive/refs/tags/1.19.3.zip",
".",
"./tests/e2e/plugins/reset-wpgraphql-logging-settings/"
]
}
},
"config": {
"WP_DEBUG": true
},
"mappings": {
".htaccess": "./wp-env/setup/.htaccess"
}
}
44 changes: 44 additions & 0 deletions plugins/wpgraphql-logging/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ tests/
├── _envs/ # Environment configs
├── _output/ # Test output (logs, coverage)
├── _support/ # Helper classes, modules
├── e2e/ # End-to-end tests (Playwright)
│ ├── specs/ # Test specifications
│ │ ├── basic-usage.spec.js
│ │ ├── data-cleanup.spec.js
│ │ └── exclude-and-sanitize.spec.js
│ ├── plugins/ # Test helper plugins
│ ├── config/ # E2E test configuration
│ ├── utils.js # Helper functions
│ ├── constants.js # Test constants
│ └── playwright.config.js # Playwright configuration
├── wpunit/ # WPUnit (WordPress-aware unit/integration) test cases
├── wpunit.suite.dist.yml
└── wpunit/
Expand Down Expand Up @@ -95,6 +105,40 @@ Automated testing runs on every pull request via GitHub Actions for a modified p
| **Codeception (WPUnit)** | Runs unit and integration tests | [View Workflow](../../actions/workflows/codeception.yml) |


## E2E Tests

End-to-end tests use Playwright to simulate real user workflows from configuring the plugin to viewing logs and managing data.

### Test Suites

| Test Suite | Description | Key Scenarios |
| -------------------------------- | -------------------------------- | -------------------------------------------------------- |
| **basic-usage.spec.js** | Core logging functionality | Enable logging, execute queries, view logs, download CSV |
| **exclude-and-sanitize.spec.js** | Query filtering and data privacy | Exclude queries, sanitize sensitive data |
| **data-cleanup.spec.js** | Data management | Configure automatic log deletion, verify cron job |

### Test Helper Plugins

Located in `tests/e2e/plugins/`:

- **`reset-wpgraphql-logging-settings`** - Resets plugin settings and clears logs table for clean test state

### Running E2E Tests

```shell
# Start wp-env (make sure Docker is running)
npm run wp-env start

# Run all E2E tests
npm run test:e2e

# Run specific test file
npm run test:e2e tests/e2e/specs/basic-usage.spec.js

# Run tests in headed mode (with browser UI)
npm run test:e2e:debug
```

>[!IMPORTANT]
> 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`.

Expand Down
2 changes: 1 addition & 1 deletion plugins/wpgraphql-logging/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"author": "wpengine",
"license": "GPL-2.0",
"devDependencies": {
"@playwright/test": "^1.52.0",
"@playwright/test": "^1.56.1",
"@wordpress/e2e-test-utils-playwright": "^1.25.0",
"@wordpress/env": "^10.25.0",
"@wordpress/jest-console": "^8.25.0",
Expand Down
25 changes: 25 additions & 0 deletions plugins/wpgraphql-logging/tests/e2e/config/global-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { request } from "@playwright/test";
import { RequestUtils } from "@wordpress/e2e-test-utils-playwright";

async function globalSetup(config) {
const { baseURL, storageState } = config.projects[0].use;
const storageStatePath =
typeof storageState === "string" ? storageState : undefined;

const requestContext = await request.newContext({
baseURL,
});

const requestUtils = new RequestUtils(requestContext, {
storageStatePath,
});

// Authenticate and save the storageState to disk.
await requestUtils.setupRest();

await Promise.all([requestUtils.resetPreferences()]);

await requestContext.dispose();
}

export default globalSetup;
21 changes: 21 additions & 0 deletions plugins/wpgraphql-logging/tests/e2e/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const PLUGIN_SLUG = "wpgraphql-logging";
export const RESET_HELPER_PLUGIN_SLUG = "reset-wpgraphql-logging-settings";

export const GET_POSTS_QUERY = `
query GetPosts {
posts(first: 5) {
nodes {
id
title
date
excerpt
author {
node {
id
name
}
}
}
}
}
`;
9 changes: 9 additions & 0 deletions plugins/wpgraphql-logging/tests/e2e/playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from "@playwright/test";
import baseConfig from "@wordpress/scripts/config/playwright.config";

const config = defineConfig({
...baseConfig,
globalSetup: require.resolve("./config/global-setup.js"),
});

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
/**
* Plugin Name: Reset WPGraphQL Logging settings
* Description: This plugin resets WPGraphQL Logging settings on activation. It's only intended to be used for e2e testing purposes.
*/

add_action('init', function () {
if ($_SERVER['REQUEST_URI'] === '/wp-admin/options-general.php?page=wpgraphql-logging&reset=true') {
global $wpdb;

// Reset settings
update_option('wpgraphql_logging_settings', array());

// Clear logs table
$table_name = $wpdb->prefix . 'wpgraphql_logging';
$wpdb->query("TRUNCATE TABLE {$table_name}");
}
});
177 changes: 177 additions & 0 deletions plugins/wpgraphql-logging/tests/e2e/specs/basic-usage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { expect, test } from "@wordpress/e2e-test-utils-playwright";
import {
goToLoggingSettingsPage,
goToLogsListPage,
configureLogging,
executeGraphQLQuery,
resetPluginSettings,
} from "../utils";
import { GET_POSTS_QUERY } from "../constants";

test.describe("Basic Logging Usage", () => {
test.beforeEach(async ({ admin, page }) => {
await resetPluginSettings(admin);

// Go to settings page
await goToLoggingSettingsPage(admin);
await expect(page.locator("h1")).toHaveText("WPGraphQL Logging Settings");
});

test("enables logging and logs GraphQL queries", async ({
page,
admin,
request,
}) => {
await configureLogging(page, {
enabled: true,
dataSampling: "100",
eventLogSelection: ["graphql_request_results"],
});

const response = await executeGraphQLQuery(request, GET_POSTS_QUERY);
expect(response.ok()).toBeTruthy();

// Check that the log appears in the logs list
await goToLogsListPage(admin);
await expect(page.locator("h1")).toContainText("WPGraphQL Logs");

const logRow = page
.locator("#the-list tr")
.filter({ hasText: "GetPosts" })
.first();
await expect(logRow).toBeVisible({ timeout: 10000 });

// View log details
const viewLink = logRow.locator(".row-actions .view a");
await expect(viewLink).toBeVisible();
await viewLink.focus();
await viewLink.click();

await expect(page.locator("h1")).toContainText("Log Entry");

const logTable = page.locator(".widefat.striped");
await expect(logTable).toBeVisible();

const queryRow = logTable
.locator("tr")
.filter({ has: page.locator("th", { hasText: "Query" }) });
await expect(queryRow).toBeVisible();
await expect(queryRow.locator("td pre")).toContainText("query GetPosts");

// Go back to logs list
const backLink = page
.locator("p a.button")
.filter({ hasText: "Back to Logs" });
await expect(backLink).toBeVisible();

await backLink.click();
await expect(page.locator("h1")).toContainText("WPGraphQL Logs");
});

test("does not log when disabled", async ({ page, admin, request }) => {
await configureLogging(page, {
enabled: false,
dataSampling: "100",
});

// Make sure there are no logs
await goToLogsListPage(admin);
await expect(
page.locator('td.colspanchange:has-text("No items found.")')
).toBeVisible();

await executeGraphQLQuery(request, GET_POSTS_QUERY);

// Navigate to logs and verify no new logs were created
await goToLogsListPage(admin);
await expect(
page.locator('td.colspanchange:has-text("No items found.")')
).toBeVisible();
});

test("downloads log as CSV with correct content", async ({
page,
admin,
request,
}) => {
await configureLogging(page, {
enabled: true,
dataSampling: "100",
eventLogSelection: ["graphql_request_results"],
});

// Execute a GraphQL query
const response = await executeGraphQLQuery(request, GET_POSTS_QUERY);
expect(response.ok()).toBeTruthy();

// Check that the log appears in the logs list
await goToLogsListPage(admin);
await expect(page.locator("h1")).toContainText("WPGraphQL Logs");

const logRow = page
.locator("#the-list tr")
.filter({ hasText: "GetPosts" })
.first();
await expect(logRow).toBeVisible({ timeout: 10000 });

// View log details
const downloadButton = logRow.locator(".row-actions .download a");
await expect(downloadButton).toBeVisible();

const downloadPromise = page.waitForEvent("download");
await downloadButton.focus();
await downloadButton.click();
const download = await downloadPromise;

// Verify download properties
expect(download.suggestedFilename()).toMatch(/graphql_log_\d+\.csv/);
expect(download.suggestedFilename()).toContain(".csv");

// Optionally save and verify the content
const path = await download.path();
const fs = require("fs");
const content = fs.readFileSync(path, "utf8");

// Verify CSV contains expected data
expect(content).toContain("ID");
expect(content).toContain("Date");
expect(content).toContain("Level");
expect(content).toContain("Message");
expect(content).toContain("GetPosts");
});

test("should set data sampling to 10% and verify only 1 log is created", async ({
page,
admin,
request,
}) => {
const QUERY_COUNT = 5;

await configureLogging(page, {
enabled: true,
dataSampling: "25",
eventLogSelection: ["graphql_request_results"],
});

// Execute a GraphQL queries
const responses = await Promise.all(
Array.from({ length: QUERY_COUNT }, async () =>
executeGraphQLQuery(request, GET_POSTS_QUERY)
)
);
await Promise.all(
responses.map(async (response) => {
return expect(response.ok()).toBeTruthy();
})
);

// Navigate to logs and verify no new logs were created
await goToLogsListPage(admin);
await expect(page.locator("h1")).toContainText("WPGraphQL Logs");

const logRow = page.locator("#the-list tr").filter({ hasText: "GetPosts" });

const logCount = await logRow.count();
expect(logCount).toBeLessThan(QUERY_COUNT);
});
});
Loading
Loading