Cannot find context with specified id"
I already reported this error as issue 263. The testcase from that issue now works well in 2.0.0-alpha.29, but the error still appears in my project. So I prepared another testcase which is failing in 2.0.0-alpha.29.
We need these two html files:
browserFrameTestParent.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Scraper test page for Browser frame methods</title>
</head>
<body>
<h1>Main Page</h1>
<div id="parentContent" >This is parent content</div>
<iframe id="testFrame" class="frameClass" src="browserFrameTestFrame.html"></iframe>
</body>
</html>
browserFrameTestFrame.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Scraper test page for Browser frame methods</title>
</head>
<body>
<h2>This is the Frame Page 1</h2>
<a id="changeTopDocument" href="doesntMatterFile.html" target="_top">Change whole page, not only the frame.</a>
</body>
</html>
And this is the test script:
import Hero from '@ulixee/hero-playground'
process.on('unhandledRejection', (reason, _) => {
if (reason instanceof Error)
console.error(`Unhandled Rejection: ${reason}`, {stack: reason.stack})
else
console.error(`Unhandled Rejection: ${reason}`)
process.exit(1)
});
(async () => {
const hero = new Hero()
await hero.goto('http://localhost/~vaclav/ulixee/browserFrameTestParent.html') // <- change this url
await hero.waitForPaintingStable()
const frameElem = await hero.document.getElementById("testFrame")
const frameEnv = await hero.mainFrameEnvironment.getFrameEnvironment(frameElem)
const anchor = await frameEnv.document.getElementById("changeTopDocument")
await anchor.scrollIntoView({block: 'end', inline: 'nearest'})
await anchor.$click('none')
console.log('Now it will crash')
await anchor.isConnected
console.log('This will not be printed. The application crash on the line above.')
await hero.close()
console.log('Done')
})();
When I run the script, then the output looks like this:
Started Ulixee Cloud at localhost:1818
Now it will crash
2024-07-19T11:01:41.004Z ERROR [hero-core/connections/ConnectionToHeroClient] ConnectionToClient.HandleRequestError {
context: {},
sessionId: '_qlBQuMWzC2wG9hgv_noX',
sessionName: undefined
} ProtocolError: Runtime.callFunctionOn: Cannot find context with specified id
at new Resolvable (/home/vaclav/sandbox/ulixee/node_modules/commons/lib/Resolvable.ts:19:18)
at createPromise (/home/vaclav/sandbox/ulixee/node_modules/commons/lib/utils.ts:147:10)
at DevtoolsSession.send (/home/vaclav/sandbox/ulixee/agent/main/lib/DevtoolsSession.ts:82:37)
at Frame.evaluateOnNode (/home/vaclav/sandbox/ulixee/agent/main/lib/Frame.ts:489:49)
at Frame.getContainerOffset (/home/vaclav/sandbox/ulixee/agent/main/lib/Frame.ts:425:47)
at async JsPath.exec (/home/vaclav/sandbox/ulixee/agent/main/lib/JsPath.ts:69:29)
at async FrameEnvironment.execJsPath (/home/vaclav/sandbox/ulixee/node_modules/core/lib/FrameEnvironment.ts:246:12)
at async CommandRecorder.runCommandFn (/home/vaclav/sandbox/ulixee/node_modules/core/lib/CommandRecorder.ts:90:16)
at async CommandRunner.runFn (/home/vaclav/sandbox/ulixee/node_modules/core/lib/CommandRunner.ts:36:14)
at async ConnectionToHeroClient.executeCommand (/home/vaclav/sandbox/ulixee/node_modules/core/connections/ConnectionToHeroClient.ts:258:12) {
method: 'Runtime.callFunctionOn',
remoteError: { code: -32000, message: 'Cannot find context with specified id' }
}
Unhandled Rejection: ProtocolError: Runtime.callFunctionOn: Cannot find context with specified id {
stack: '\n' +
'\n' +
' ---> await anchor.isConnected\n' +
'\n' +
'\n' +
'ProtocolError: Runtime.callFunctionOn: Cannot find context with specified id\n' +
' at new Resolvable (/home/vaclav/sandbox/ulixee/node_modules/commons/lib/Resolvable.ts:19:18)\n' +
' at createPromise (/home/vaclav/sandbox/ulixee/node_modules/commons/lib/utils.ts:147:10)\n' +
' at DevtoolsSession.send (/home/vaclav/sandbox/ulixee/agent/main/lib/DevtoolsSession.ts:82:37)\n' +
' at Frame.evaluateOnNode (/home/vaclav/sandbox/ulixee/agent/main/lib/Frame.ts:489:49)\n' +
' at Frame.getContainerOffset (/home/vaclav/sandbox/ulixee/agent/main/lib/Frame.ts:425:47)\n' +
' at async JsPath.exec (/home/vaclav/sandbox/ulixee/agent/main/lib/JsPath.ts:69:29)\n' +
' at async FrameEnvironment.execJsPath (/home/vaclav/sandbox/ulixee/node_modules/core/lib/FrameEnvironment.ts:246:12)\n' +
' at async CommandRecorder.runCommandFn (/home/vaclav/sandbox/ulixee/node_modules/core/lib/CommandRecorder.ts:90:16)\n' +
' at async CommandRunner.runFn (/home/vaclav/sandbox/ulixee/node_modules/core/lib/CommandRunner.ts:36:14)\n' +
' at async ConnectionToHeroClient.executeCommand (/home/vaclav/sandbox/ulixee/node_modules/core/connections/ConnectionToHeroClient.ts:258:12)\n' +
'------REMOTE CORE---------------------------------\n' +
' at Function.reviver (/home/vaclav/sandbox/ulixee/node_modules/commons/lib/TypeSerializer.ts:251:26)\n' +
' at JSON.parse (<anonymous>)\n' +
' at Function.parse (/home/vaclav/sandbox/ulixee/node_modules/commons/lib/TypeSerializer.ts:31:17)\n' +
' at WsTransportToCore.onMessage (/home/vaclav/sandbox/ulixee/node_modules/net/lib/WsTransportToCore.ts:105:36)\n' +
' at WebSocket.emit (node:events:517:28)\n' +
' at Receiver.receiverOnMessage (/home/vaclav/sandbox/ulixee/node_modules/ws/lib/websocket.js:1220:20)\n' +
' at Receiver.emit (node:events:517:28)\n' +
' at Receiver.dataMessage (/home/vaclav/sandbox/ulixee/node_modules/ws/lib/receiver.js:596:14)\n' +
' at /home/vaclav/sandbox/ulixee/node_modules/ws/lib/receiver.js:530:12\n' +
' at /home/vaclav/sandbox/ulixee/node_modules/ws/lib/permessage-deflate.js:309:9\n' +
'------CONNECTION----------------------------------\n' +
' at new Resolvable (/home/vaclav/sandbox/ulixee/node_modules/commons/lib/Resolvable.ts:19:18)\n' +
' at createPromise (/home/vaclav/sandbox/ulixee/node_modules/commons/lib/utils.ts:147:10)\n' +
' at PendingMessages.create (/home/vaclav/sandbox/ulixee/node_modules/net/lib/PendingMessages.ts:47:44)\n' +
' at ConnectionToHeroCore.sendRequest (/home/vaclav/sandbox/ulixee/node_modules/net/lib/ConnectionToCore.ts:158:50)\n' +
' at async CoreCommandQueue.sendRequest (/home/vaclav/sandbox/ulixee/node_modules/client/lib/CoreCommandQueue.ts:317:12)\n' +
' at async Object.cb (/home/vaclav/sandbox/ulixee/node_modules/client/lib/CoreCommandQueue.ts:231:16)\n' +
' at async Queue.next (/home/vaclav/sandbox/ulixee/node_modules/commons/lib/Queue.ts:188:19)\n' +
'------CORE COMMANDS-------------------------------\n' +
' at Queue.run (/home/vaclav/sandbox/ulixee/node_modules/commons/lib/Queue.ts:63:19)\n' +
' at CoreCommandQueue.run (/home/vaclav/sandbox/ulixee/node_modules/client/lib/CoreCommandQueue.ts:220:8)\n' +
' at CoreFrameEnvironment.execJsPath (/home/vaclav/sandbox/ulixee/node_modules/client/lib/CoreFrameEnvironment.ts:80:36)\n' +
' at execJsPath (/home/vaclav/sandbox/ulixee/node_modules/client/lib/SetupAwaitedHandler.ts:160:26)\n' +
' at Object.getProperty (/home/vaclav/sandbox/ulixee/node_modules/client/lib/SetupAwaitedHandler.ts:43:24)\n' +
' at async AwaitedHandler.getProperty (/home/vaclav/sandbox/ulixee/node_modules/files/2-finalized/awaited-dom/base/AwaitedHandler.ts:25:12)\n' +
' at async /home/vaclav/sandbox/ulixee/app.ts:23:5\n' +
'\n' +
'--------------------------------------------------\n' +
'--------------------------------------------------\n' +
'------_qlBQuMWzC2wG9hgv_noX-----------------------\n' +
'--------------------------------------------------'
}
Process finished with exit code 1
I hope you’re doing well. Would you be able to find some time to look into the reported issue and work on a fix? Your assistance would be greatly appreciated.
@eNcacz I'd love to find time to take a look at this but my plate is still quite full. If you have any ability to wire up a unit test like we did for the last one and see where it's breaking (it will be somewhere in agent/lib/FramesManager or Frame), that will be your best option for a quicker resolution
@blakebyrnes I looked into the problem and found the cause in the Frame in @ulixee/unblocked-agent.
It is caused by:
public async getContainerOffset(): Promise<IPoint> {
// This gets called on .isConnected() and this.getBoundingClientRect(); causes problem
// if context changes and node with the frameElementNodeId no longer exists.
// It can happen besause this.getFrameElementDevtoolsNodeId(); gets the real node id once
// and on every subsequent call it returns the cached value.
if (!this.parentId) return { x: 0, y: 0 };
const parentOffset = await this.parentFrame.getContainerOffset();
const frameElementNodeId = await this.getFrameElementDevtoolsNodeId();
const thisOffset = await this.parentFrame.evaluateOnNode<IPoint>(
frameElementNodeId,
`(() => {
const rect = this.getBoundingClientRect();
return { x:rect.x, y:rect.y };
})()`,
);
return {
x: thisOffset.x + parentOffset.x,
y: thisOffset.y + parentOffset.y,
};
}
and can be solved with change in evaluateOnNode:
public async evaluateOnNode<T>(devtoolsObjectId: string, expression: string): Promise<T> {
if (this.closedWithError) throw this.closedWithError;
try {
// PROPOSED SOLUTION - Test if the node is still there,
// if it is not the Runtime.callFunctionOn will throw an unhandled error.
// Other solution would be test this in getFrameElementDevtoolsNodeId() and throw error if it is not there.
await this.parentFrame.devtoolsSession.send('DOM.getFrameOwner', { frameId: this.id }, this);
const result = await this.devtoolsSession.send(
'Runtime.callFunctionOn',
{
functionDeclaration: `function executeRemoteFn() {
return ${expression};
}`,
returnByValue: true,
objectId: devtoolsObjectId,
},
this,
);
if (result.exceptionDetails) {
throw ConsoleMessage.exceptionToError(result.exceptionDetails);
}
const remote = result.result;
if (remote.objectId) this.devtoolsSession.disposeRemoteObject(remote);
return remote.value as T;
} catch (err) {
if (err instanceof CanceledPromiseError) return;
throw err;
}
}
@kumbalek Nice! Did you test that this works? Or just think this is probably going to fix it?
@blakebyrnes I re-tested it and found better solution that works and fixes @eNcacz problem.
JsPath.ts
public async exec<T>(jsPath: IJsPath, timeoutMs?: number): Promise<IExecJsPathResult<T>> {
await this.frame.navigationsObserver.waitForLoad(LoadStatus.JavascriptReady, {
timeoutMs: timeoutMs ?? 30e3,
doNotIncrementMarker: true,
});
try {
const containerOffset = await this.frame.getContainerOffset();
return this.runJsPath<T>(`exec`, jsPath, containerOffset);
} catch (error) {
if (jsPath.at(-1) === 'isConnected') {
this.logger.warn('Error in getting this.frame.ContainerOffset()', {
error,
});
return Promise.resolve({ value: false } as any);
}
throw error;
}
}
This catches the context error in getContainerOffset(); and if it is request for isConnected it returns false indicating it is not connected.
To be polished:
- I dont know how the jsPath might look like, I expect
isConnectedmethod at the end of the array, but if it can be somewhere else adjust the condition toif (jsPath.includes('isConnected')) - You can add additional check in the condition for the
err.code === ContextNotFoundCodeso that this fix will apply only on this specific problem and not any other error that might arise inthis.frame.ContainerOffset(). But I assumed if there is an error,isConnectedshould returnfalse.