[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f30kogjgjjlsa6":3},{"_id":4,"slug":5,"title":6,"subtitle":7,"kind":8,"cards":9,"tags":58,"categories":60,"source":62,"lang":65,"author":66,"audioState":69,"stats":70,"publishedAt":73,"renderer":74},"6abcab79ca21c797c7ea0425","how-an-agent-verifies-a-webpage-it-can-never-look-at-029413cb","How an agent verifies a webpage it can never look at","Fifth in a series on using @relax.js\u002Fcore with a coding agent.","news",[10,13,18,23,28,33,38,43,48,53],{"headline":6,"body":11,"imageUrl":12,"sourceImageUrl":12},"Fifth in a series on using @relax.js\u002Fcore with a coding agent. The previous piece was about seeing errors in a test. This one is about getting the component into a state where there is something to see. Everything here is from @relax.js\u002Fcore\u002Ftesting. Why plain jsdom is not enough An agent that knows Web Components will write this and be puzzled:","https:\u002F\u002Fmedia2.dev.to\u002Fdynamic\u002Fimage\u002Fwidth=1200,height=627,fit=cover,gravity=auto,format=auto\u002Fhttps%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwxmvvh2ja5c3nbg0jdk2.png",{"headline":14,"body":15,"imageUrl":16,"images":17},"connectedCallback only runs when the element is in","connectedCallback only runs when the element is in a document. Created and never attached, it is inert, for reasons that have nothing to do with the component. And once attached, anything it awaits finishes a task later, so asserting right after attaching sees the element before its data arrived.","\u002Fapi\u002Fmedia\u002Fposts\u002Fhow-an-agent-verifies-a-webpage-it-can-never-look-at-029413cb\u002F1.webp",{"local":16},{"headline":19,"body":20,"imageUrl":21,"images":22},"Two helpers cover this. mount() attaches to document.body","Two helpers cover this. mount() attaches to document.body and hands back unmount(), which is also how you test disconnectedCallback:","\u002Fapi\u002Fmedia\u002Fposts\u002Fhow-an-agent-verifies-a-webpage-it-can-never-look-at-029413cb\u002F2.webp",{"local":21},{"headline":24,"body":25,"imageUrl":26,"images":27},"flush() waits for pending microtasks and the next","flush() waits for pending microtasks and the next macrotask, so work started by a lifecycle callback has landed. You will see it after every user action in the page tests below. Fake the server, not the component","\u002Fapi\u002Fmedia\u002Fposts\u002Fhow-an-agent-verifies-a-webpage-it-can-never-look-at-029413cb\u002F3.webp",{"local":26},{"headline":29,"body":30,"imageUrl":31,"images":32},"The page loads its data through @relax.js\u002Fcore\u002Fhttp. fakeServer()","The page loads its data through @relax.js\u002Fcore\u002Fhttp. fakeServer() replaces the network underneath that module with canned responses and records every request:","\u002Fapi\u002Fmedia\u002Fposts\u002Fhow-an-agent-verifies-a-webpage-it-can-never-look-at-029413cb\u002F4.webp",{"local":31},{"headline":34,"body":35,"imageUrl":36,"images":37},"Paths include the configured base URL and exclude","Paths include the configured base URL and exclude the query string, because that is what a server sees. A request nothing was registered for gets a 404 whose body lists what is registered, and it is recorded like any other, so an unexpected call shows up in server.requests instead of hanging or reaching the network.","\u002Fapi\u002Fmedia\u002Fposts\u002Fhow-an-agent-verifies-a-webpage-it-can-never-look-at-029413cb\u002F5.webp",{"local":36},{"headline":39,"body":40,"imageUrl":41,"images":42},"The seam is deliberate. Do not stub fetch","The seam is deliberate. Do not stub fetch globally, and do not mock the component's own load() method. The component under test is the real one, and the assertion covers both what it rendered and what it asked for: A function body is called with the request, which is how a PUT answers with what it was sent:","\u002Fapi\u002Fmedia\u002Fposts\u002Fhow-an-agent-verifies-a-webpage-it-can-never-look-at-029413cb\u002F6.webp",{"local":41},{"headline":44,"body":45,"imageUrl":46,"images":47},"Note requestSubmit(), not submit(). The latter skips the","Note requestSubmit(), not submit(). The latter skips the submit event, and the FormValidator that owns the form never hears about it. When an agent picks the wrong one, the test says so: the PUT is missing from server.requests. The failure path is one more on() with a status: Registration order matters: the first match wins. That is why the PUT is registered per test and not in beforeEach.","\u002Fapi\u002Fmedia\u002Fposts\u002Fhow-an-agent-verifies-a-webpage-it-can-never-look-at-029413cb\u002F7.webp",{"local":46},{"headline":49,"body":50,"imageUrl":51,"images":52},"The page above is reached with routing.navigate(), not","The page above is reached with routing.navigate(), not mount(). The difference is everything a page depends on: loadRoute() runs with the route parameters before the element is added, the element lands inside \u003Cr-route-target>, and routeData is set. mountRouting() registers the routes and puts the targets in the document; its navigate() resolves with the rendered component once it is inside the target, after all of that. No flush() needed for the load itself.","\u002Fapi\u002Fmedia\u002Fposts\u002Fhow-an-agent-verifies-a-webpage-it-can-never-look-at-029413cb\u002F8.webp",{"local":51},{"headline":54,"body":55,"imageUrl":56,"images":57},"It rejects the way navigate() throws in the","It rejects the way navigate() throws in the application: no route matched, or a guard stopped it. When the component never appears within the timeout, the rejection lists the errors reported meanwhile, so a loadRoute() that threw is named instead of leaving you with an empty target. The routes it takes are the app's own:","\u002Fapi\u002Fmedia\u002Fposts\u002Fhow-an-agent-verifies-a-webpage-it-can-never-look-at-029413cb\u002F9.webp",{"local":56},[59],"dev",[61],"Technology",{"name":63,"url":64},"Dev.to","https:\u002F\u002Fdev.to\u002Fjgauffin\u002Fhow-an-agent-verifies-a-webpage-it-can-never-look-at-33mh","en",{"handle":67,"displayName":68},"spots","Spots","queued",{"views":71,"likes":72,"saves":72,"shares":72,"completions":72,"opens":72,"skips":72,"depthSum":72},1,0,"2026-09-30T06:26:01.434Z","local"]