Asserts Chrome extension message-passing behaviour against a running extension: one-shot `chrome.runtime.sendMessage` plus the literal `return true` that holds the response channel open for an async `sendResponse`, `chrome.tabs.sendMessage` into one tab's content script, long-lived `chrome.runtime.connect` ports and their `onDisconnect` triggers, web-page messages gated by `externally_connectable`, and `chrome.runtime.connectNative` native-messaging hosts. Covers the payload rules a test must respect (Chrome uses JSON serialization rather than structured clone, so `Map` / `Set` / `Date` do not round-trip; maximum message size is 64 MiB) and the first-listener-wins rule when several `onMessage` listeners are registered. Scope is messaging behaviour on an already-running extension, not the install or reload step. Use when a message reaches no listener, a `sendResponse` callback never fires, a port disconnects mid-test, or a page origin has to be proven allowed before publishing.
79
99%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Deep reference for chrome-extension-messaging-tests SKILL.md. Consult when a
long-lived chrome.runtime.connect port drops mid-session or a port test has to
account for service-worker suspension.
Per the Chrome Extensions message passing page, a port is opened with
chrome.runtime.connect and answered by onConnect:
// Content script
const port = chrome.runtime.connect({ name: 'knockknock' });
port.onMessage.addListener(msg => {
if (msg.question === "Who's there?") port.postMessage({ answer: 'Madame' });
});
port.postMessage({ joke: 'Knock knock' });
// Service worker
chrome.runtime.onConnect.addListener(port => {
if (port.name !== 'knockknock') return;
port.onMessage.addListener(msg => {
if (msg.joke === 'Knock knock') port.postMessage({ question: "Who's there?" });
});
});The chrome.runtime reference gives
chrome.runtime.connect(extensionId?, connectInfo?) returning a Port, where
connectInfo.name "will be passed into onConnect for processes that are
listening for the connection event". That name is what the
port.name !== 'knockknock' guard above filters on, so a test that connects
with the wrong name should observe no reply.
Per cr-msg, onDisconnect fires when: no listeners exist for onConnect, the
tab unloads, the originating frame unloads, all receiving frames unload, or
disconnect() is called. A port test should cover at least the first and last
of those, because they are the two a bug produces:
test('connecting with an unknown port name disconnects immediately', async () => {
const events = [];
const port = chrome.runtime.connect({ name: 'no-such-listener' });
port.onDisconnect.addListener(() => events.push('disconnect'));
await settle();
expect(events).toEqual(['disconnect']);
});Accumulate disconnect events into an array rather than setting a boolean. A port can have multiple receiving frames per cr-msg, so "exactly one disconnect" is an assertion you should make explicitly rather than assume.
Per the extension service worker lifecycle page, the service worker terminates after 30 seconds of inactivity, and "Opening a port no longer resets the timers" as of Chrome 114. What keeps it alive is traffic: cr-sw states "Sending a message with long-lived messaging keeps the service worker alive". A port test that idles longer than 30 seconds without exchanging a message is asserting against a terminated worker, and the disconnect it observes is the platform, not a bug.
onDisconnect conditions, multiple
receiving frames):
https://developer.chrome.com/docs/extensions/develop/concepts/messagingchrome.runtime API reference (connect / connectInfo.name):
https://developer.chrome.com/docs/extensions/reference/api/runtime