Merge pull request #243 from getmaxun/navigation-fix

fix: handle context destroyed and frame navigation
This commit is contained in:
Karishma Shukla
2024-12-10 22:38:47 +05:30
committed by GitHub
2 changed files with 88 additions and 34 deletions

View File

@@ -469,6 +469,16 @@ export default class Interpreter extends EventEmitter {
}), }),
}; };
const executeAction = async (invokee: any, methodName: string, args: any) => {
console.log("Executing action:", methodName, args);
if (!args || Array.isArray(args)) {
await (<any>invokee[methodName])(...(args ?? []));
} else {
await (<any>invokee[methodName])(args);
}
};
for (const step of steps) { for (const step of steps) {
this.log(`Launching ${String(step.action)}`, Level.LOG); this.log(`Launching ${String(step.action)}`, Level.LOG);
@@ -486,10 +496,20 @@ export default class Interpreter extends EventEmitter {
invokee = invokee[level]; invokee = invokee[level];
} }
if (!step.args || Array.isArray(step.args)) { if (methodName === 'waitForLoadState') {
await (<any>invokee[methodName])(...(step.args ?? [])); try {
await executeAction(invokee, methodName, step.args);
} catch (error) {
await executeAction(invokee, methodName, 'domcontentloaded');
}
} else if (methodName === 'click') {
try {
await executeAction(invokee, methodName, step.args);
} catch (error) {
await executeAction(invokee, methodName, [step.args[0], { force: true }]);
}
} else { } else {
await (<any>invokee[methodName])(step.args); await executeAction(invokee, methodName, step.args);
} }
} }
@@ -571,7 +591,7 @@ export default class Interpreter extends EventEmitter {
return allResults; return allResults;
} }
// Click the 'Load More' button to load additional items // Click the 'Load More' button to load additional items
await loadMoreButton.click(); await loadMoreButton.dispatchEvent('click');
await page.waitForTimeout(2000); // Wait for new items to load await page.waitForTimeout(2000); // Wait for new items to load
// After clicking 'Load More', scroll down to load more items // After clicking 'Load More', scroll down to load more items
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));

View File

@@ -66,6 +66,8 @@ export class RemoteBrowser {
maxRepeats: 1, maxRepeats: 1,
}; };
private lastEmittedUrl: string | null = null;
/** /**
* {@link WorkflowGenerator} instance specific to the remote browser. * {@link WorkflowGenerator} instance specific to the remote browser.
*/ */
@@ -88,6 +90,64 @@ export class RemoteBrowser {
this.generator = new WorkflowGenerator(socket); this.generator = new WorkflowGenerator(socket);
} }
/**
* Normalizes URLs to prevent navigation loops while maintaining consistent format
*/
private normalizeUrl(url: string): string {
try {
const parsedUrl = new URL(url);
// Remove trailing slashes except for root path
parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, '') || '/';
// Ensure consistent protocol handling
parsedUrl.protocol = parsedUrl.protocol.toLowerCase();
return parsedUrl.toString();
} catch {
return url;
}
}
/**
* Determines if a URL change is significant enough to emit
*/
private shouldEmitUrlChange(newUrl: string): boolean {
if (!this.lastEmittedUrl) {
return true;
}
const normalizedNew = this.normalizeUrl(newUrl);
const normalizedLast = this.normalizeUrl(this.lastEmittedUrl);
return normalizedNew !== normalizedLast;
}
private async setupPageEventListeners(page: Page) {
page.on('framenavigated', async (frame) => {
if (frame === page.mainFrame()) {
const currentUrl = page.url();
if (this.shouldEmitUrlChange(currentUrl)) {
this.lastEmittedUrl = currentUrl;
this.socket.emit('urlChanged', currentUrl);
}
}
});
// Handle page load events with retry mechanism
page.on('load', async () => {
const injectScript = async (): Promise<boolean> => {
try {
await page.waitForLoadState('networkidle', { timeout: 5000 });
await page.evaluate(getInjectableScript());
return true;
} catch (error: any) {
logger.log('warn', `Script injection attempt failed: ${error.message}`);
return false;
}
};
const success = await injectScript();
console.log("Script injection result:", success);
});
}
/** /**
* An asynchronous constructor for asynchronously initialized properties. * An asynchronous constructor for asynchronously initialized properties.
* Must be called right after creating an instance of RemoteBrowser class. * Must be called right after creating an instance of RemoteBrowser class.
@@ -167,15 +227,7 @@ export class RemoteBrowser {
this.context = await this.browser.newContext(contextOptions); this.context = await this.browser.newContext(contextOptions);
this.currentPage = await this.context.newPage(); this.currentPage = await this.context.newPage();
this.currentPage.on('framenavigated', (frame) => { await this.setupPageEventListeners(this.currentPage);
if (frame === this.currentPage?.mainFrame()) {
this.socket.emit('urlChanged', this.currentPage.url());
}
});
this.currentPage.on('load', (page) => {
page.evaluate(getInjectableScript())
})
// await this.currentPage.setExtraHTTPHeaders({ // await this.currentPage.setExtraHTTPHeaders({
// 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
@@ -375,15 +427,7 @@ export class RemoteBrowser {
await this.stopScreencast(); await this.stopScreencast();
this.currentPage = page; this.currentPage = page;
this.currentPage.on('framenavigated', (frame) => { await this.setupPageEventListeners(this.currentPage);
if (frame === this.currentPage?.mainFrame()) {
this.socket.emit('urlChanged', this.currentPage.url());
}
});
this.currentPage.on('load', (page) => {
page.evaluate(getInjectableScript())
})
//await this.currentPage.setViewportSize({ height: 400, width: 900 }) //await this.currentPage.setViewportSize({ height: 400, width: 900 })
this.client = await this.currentPage.context().newCDPSession(this.currentPage); this.client = await this.currentPage.context().newCDPSession(this.currentPage);
@@ -411,18 +455,8 @@ export class RemoteBrowser {
await this.currentPage?.close(); await this.currentPage?.close();
this.currentPage = newPage; this.currentPage = newPage;
if (this.currentPage) { if (this.currentPage) {
this.currentPage.on('framenavigated', (frame) => { await this.setupPageEventListeners(this.currentPage);
if (frame === this.currentPage?.mainFrame()) {
this.socket.emit('urlChanged', this.currentPage.url());
}
});
this.currentPage.on('load', (page) => {
page.evaluate(getInjectableScript())
})
// this.currentPage.on('load', (page) => {
// this.socket.emit('urlChanged', page.url());
// })
this.client = await this.currentPage.context().newCDPSession(this.currentPage); this.client = await this.currentPage.context().newCDPSession(this.currentPage);
await this.subscribeToScreencast(); await this.subscribeToScreencast();
} else { } else {