Skip to content

Commit 7755e17

Browse files
committed
Update QM e2e test for both QM 3.x and 4.0+
Add separate test cases for QM 3.x (server-side HTML) and QM 4.0+ (Preact shadow DOM). Each test auto-skips when the other QM version is detected.
1 parent f064a7e commit 7755e17

1 file changed

Lines changed: 159 additions & 5 deletions

File tree

tests/e2e/specs/query-monitor-plugin.test.js

Lines changed: 159 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,169 @@ test.describe( 'Query Monitor plugin', () => {
1212
expect( plugin.status ).toBe( 'inactive' );
1313
}
1414

15-
test.beforeEach( async ( { requestUtils }) => {
15+
test.beforeEach( async ( { requestUtils } ) => {
1616
await deactivateQueryMonitor( requestUtils );
1717
} );
1818

19-
test.afterEach( async ( { requestUtils }) => {
19+
test.afterEach( async ( { requestUtils } ) => {
2020
await deactivateQueryMonitor( requestUtils );
2121
} );
2222

23-
test( 'should activate', async ( { admin, page } ) => {
23+
test( 'should activate and show SQLite queries (QM 4.0+)', async ( { admin, page } ) => {
2424
// Activate the Query Monitor plugin on the plugins page.
2525
await admin.visitAdminPage( '/plugins.php' );
2626
await page.getByLabel( 'Activate Query Monitor', { exact: true } ).click();
2727
await page.getByText( 'Plugin activated.', { exact: true } ).waitFor();
2828

29+
// Skip this test if QM 4.0+ is not active (no shadow DOM container).
30+
const hasContainer = await page.locator( '#query-monitor-container' ).count();
31+
test.skip( hasContainer === 0, 'QM 4.0+ not detected' );
32+
33+
// Click on the Query Monitor admin bar item.
34+
// QM 4.0 re-renders the admin bar with Preact — use the ab-item class.
35+
await page.locator( '#wp-admin-bar-query-monitor > a.ab-item' ).click();
36+
37+
// Wait for the QM panel to render inside the shadow DOM.
38+
const container = page.locator( '#query-monitor-container' );
39+
await expect( async () => {
40+
const hasShadow = await container.evaluate(
41+
( el ) => el.shadowRoot !== null
42+
);
43+
expect( hasShadow ).toBe( true );
44+
} ).toPass();
45+
46+
// Click on the Database Queries tab inside the shadow DOM.
47+
// QM 4.0 renders nav items as <button role="tab">.
48+
await expect( async () => {
49+
await container.evaluate( ( el ) => {
50+
const shadow = el.shadowRoot;
51+
const tabs = shadow.querySelectorAll( 'button[role="tab"]' );
52+
for ( const tab of tabs ) {
53+
if ( tab.textContent.includes( 'Database Queries' ) ) {
54+
tab.click();
55+
return;
56+
}
57+
}
58+
throw new Error( 'Database Queries tab not found' );
59+
} );
60+
} ).toPass();
61+
62+
// Verify the first logged query is visible in the shadow DOM.
63+
await expect( async () => {
64+
const hasSqlQuery = await container.evaluate( ( el ) => {
65+
const shadow = el.shadowRoot;
66+
const codeCells = shadow.querySelectorAll( 'td code' );
67+
for ( const cell of codeCells ) {
68+
if ( cell.textContent.includes( 'SELECT option_name, option_value' ) ) {
69+
return true;
70+
}
71+
}
72+
return false;
73+
} );
74+
expect( hasSqlQuery ).toBe( true );
75+
} ).toPass();
76+
77+
// Click the SQLite <details> summary for the first query row.
78+
// The element is injected by a debounced MutationObserver, so retry.
79+
await expect( async () => {
80+
await container.evaluate( ( el ) => {
81+
const shadow = el.shadowRoot;
82+
const summary = shadow.querySelector( 'details.qm-sqlite summary' );
83+
if ( ! summary ) {
84+
throw new Error( 'SQLite details summary not found' );
85+
}
86+
summary.click();
87+
} );
88+
} ).toPass();
89+
90+
// Verify the SQLite query is displayed.
91+
await expect( async () => {
92+
const hasSqliteQuery = await container.evaluate( ( el ) => {
93+
const shadow = el.shadowRoot;
94+
const sqliteQueries = shadow.querySelectorAll( '.qm-sqlite-query' );
95+
for ( const query of sqliteQueries ) {
96+
if (
97+
query.textContent.includes( 'SELECT' ) &&
98+
query.textContent.includes( 'option_name' )
99+
) {
100+
return true;
101+
}
102+
}
103+
return false;
104+
} );
105+
expect( hasSqliteQuery ).toBe( true );
106+
} ).toPass();
107+
108+
// Apply a Caller filter on the DB Queries table and verify our
109+
// <details> stay attached to the (filtered) visible rows. QM removes
110+
// hidden rows from the DOM and renumbers them, so this would break
111+
// any index-based lookup.
112+
const setCallerFilter = ( value ) =>
113+
container.evaluate( ( el, v ) => {
114+
const panel = el.shadowRoot.getElementById( 'qm-db_queries' );
115+
const callerSelect = panel?.querySelectorAll( 'thead select' )[ 0 ];
116+
if ( ! callerSelect ) {
117+
throw new Error( 'Caller filter select not found' );
118+
}
119+
callerSelect.value = v;
120+
callerSelect.dispatchEvent( new Event( 'change', { bubbles: true } ) );
121+
}, value );
122+
123+
await expect( () => setCallerFilter( 'get_option' ) ).toPass();
124+
125+
await expect( async () => {
126+
const counts = await container.evaluate( ( el ) => {
127+
const panel = el.shadowRoot.getElementById( 'qm-db_queries' );
128+
return {
129+
rows: panel.querySelectorAll( 'tbody tr' ).length,
130+
details: panel.querySelectorAll( 'details.qm-sqlite' ).length,
131+
};
132+
} );
133+
expect( counts.rows ).toBeGreaterThan( 0 );
134+
expect( counts.details ).toBe( counts.rows );
135+
} ).toPass();
136+
137+
// Reset the filter.
138+
await setCallerFilter( '' );
139+
140+
// Switch to "Queries by Caller" and verify no SQLite details bleed
141+
// into the sub-panel — we explicitly scope injection to the main
142+
// "Database Queries" panel.
143+
await expect( async () => {
144+
await container.evaluate( ( el ) => {
145+
const shadow = el.shadowRoot;
146+
const tabs = shadow.querySelectorAll( 'button[role="tab"]' );
147+
for ( const tab of tabs ) {
148+
if ( tab.textContent.includes( 'Queries by Caller' ) ) {
149+
tab.click();
150+
return;
151+
}
152+
}
153+
throw new Error( 'Queries by Caller tab not found' );
154+
} );
155+
} ).toPass();
156+
157+
await expect( async () => {
158+
const detailsCount = await container.evaluate( ( el ) => {
159+
const shadow = el.shadowRoot;
160+
return shadow.querySelectorAll( 'details.qm-sqlite' ).length;
161+
} );
162+
expect( detailsCount ).toBe( 0 );
163+
} ).toPass();
164+
} );
165+
166+
test( 'should activate and show SQLite queries (QM 3.x)', async ( { admin, page } ) => {
167+
// Activate the Query Monitor plugin on the plugins page.
168+
await admin.visitAdminPage( '/plugins.php' );
169+
await page.getByLabel( 'Activate Query Monitor', { exact: true } ).click();
170+
await page.getByText( 'Plugin activated.', { exact: true } ).waitFor();
171+
172+
// Skip this test if QM 3.x is not active (has shadow DOM container = QM 4.0+).
173+
const hasContainer = await page.locator( '#query-monitor-container' ).count();
174+
test.skip( hasContainer > 0, 'QM 3.x not detected' );
175+
29176
// Click on the Query Monitor menu item in the WordPress admin bar.
30-
await page.locator('a[role="menuitem"][href="#qm-overview"][aria-expanded="false"]').click();
177+
await page.locator( '#wp-admin-bar-query-monitor > a' ).click();
31178

32179
// Wait for the Query Monitor panel to open.
33180
await page.locator( '#query-monitor-main' ).waitFor();
@@ -42,6 +189,13 @@ test.describe( 'Query Monitor plugin', () => {
42189

43190
// Check that the query is logged with SQLite information.
44191
await sqlCell.getByLabel( 'Toggle SQLite queries' ).click();
45-
expect( page.locator('.qm-sqlite-query', { hasText: 'SELECT `option_name` , `option_value` FROM `wp_options`' }).first() ).toBeVisible();
192+
await expect(
193+
page
194+
.locator( '.qm-sqlite-query', {
195+
hasText:
196+
'SELECT `option_name` , `option_value` FROM `wp_options`',
197+
} )
198+
.first()
199+
).toBeVisible();
46200
} );
47201
} );

0 commit comments

Comments
 (0)