Skip to content

Commit 2f86ffe

Browse files
committed
fix(twitter): replace search input approach with pushState+popstate SPA navigation
The previous approach (nativeSetter + Enter keydown on the search input) does not reliably trigger Twitter's form submission - the synthetic KeyboardEvent is ignored by React, leaving the page on /explore with zero API calls captured. Use history.pushState + PopStateEvent instead, which triggers React Router's listener and performs a true SPA navigation to /search. The interceptor survives because no full page reload occurs. Tested: "opencli", "it's a test" (single quote), "hello" all return results with correct author attribution.
1 parent 10af754 commit 2f86ffe

1 file changed

Lines changed: 10 additions & 34 deletions

File tree

src/clis/twitter/search.ts

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,46 +24,22 @@ cli({
2424
// fetch will capture the SearchTimeline API call.
2525
await page.installInterceptor('SearchTimeline');
2626

27-
// 3. Use the search input to submit the query (SPA, no full reload).
28-
// Find the search input, type the query, and submit.
27+
// 3. Trigger SPA navigation to search results via history API.
28+
// pushState + popstate triggers React Router's listener without
29+
// a full page reload, so the interceptor stays alive.
30+
// Note: the previous approach (nativeSetter + Enter keydown on the
31+
// search input) does not reliably trigger Twitter's form submission.
32+
const searchUrl = JSON.stringify(`/search?q=${encodeURIComponent(query)}&f=top`);
2933
await page.evaluate(`
3034
(() => {
31-
const input = document.querySelector('input[data-testid="SearchBox_Search_Input"]');
32-
if (!input) throw new Error('Search input not found');
33-
input.focus();
34-
const nativeSetter = Object.getOwnPropertyDescriptor(
35-
HTMLInputElement.prototype, 'value'
36-
).set;
37-
nativeSetter.call(input, ${JSON.stringify(query)});
38-
input.dispatchEvent(new Event('input', { bubbles: true }));
39-
})()
40-
`);
41-
await page.wait(0.5);
42-
// Press Enter to submit
43-
await page.evaluate(`
44-
(() => {
45-
const input = document.querySelector('input[data-testid="SearchBox_Search_Input"]');
46-
if (!input) throw new Error('Search input not found');
47-
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
35+
window.history.pushState({}, '', ${searchUrl});
36+
window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));
4837
})()
4938
`);
5039
await page.wait(5);
5140

52-
// 4. Click "Top" tab if available (ensures we get top results)
53-
try {
54-
await page.evaluate(`
55-
(() => {
56-
const tabs = document.querySelectorAll('[role="tab"]');
57-
for (const tab of tabs) {
58-
if (tab.textContent.trim() === 'Top') { tab.click(); break; }
59-
}
60-
})()
61-
`);
62-
await page.wait(2);
63-
} catch { /* ignore if tab not found */ }
64-
65-
// 5. Scroll to trigger additional pagination
66-
await page.autoScroll({ times: 2, delayMs: 2000 });
41+
// 4. Scroll to trigger additional pagination
42+
await page.autoScroll({ times: 3, delayMs: 2000 });
6743

6844
// 6. Retrieve captured data
6945
const requests = await page.getInterceptedRequests();

0 commit comments

Comments
 (0)