I recently had a horrible experience where I could not get Lit to run the same test twice using the WebdriverIO Component Testing Guidelines for Lit.

The test is simple: Before each test, render the component, click on it, see if its internal state is what I expect, and then remove the component at the end of the test. I expected that if I ran the exact same test twice, it would work (or fail) twice.

It turns out Lit is being passive-aggressive about caching what you've done, won't re-render cached content preferring to re-connect it instead, and won't help you figure out that it doesn't exist anymore and can't be re-connected.

This is the failing version:

describe("Search select: Test Input Field", () => {
    let select: WebdriveIO.Element;
    
    beforeEach(async () => {
        render(
            html`<ak-search-select-view .options=${longGoodForYouPairs} blankable>
                 </ak-search-select-view>`,
            document.body);
        select = await $("ak-search-select-view");
    });

    it("should open the panel when the input is clicked", async () => {
        expect(await select.getProperty("inputState")).toBe(0);
        await (await select.$(">>>input")).click();
        expect(await select.getProperty("inputState")).toBe(2);
    });

    it("should open the panel when the input is clicked a second time", async () => {
        expect(await select.getProperty("inputState")).toBe(0);
        await (await select.$(">>>input")).click();
        expect(await select.getProperty("inputState")).toBe(2);
    });
    
    afterEach(saync() => {
        await document.body.querySelect("ak-search-select-view").remove();
    });
});

This would pass the first test and fail on the second every time, declaring that the element couldn't be found. I was going out of my mind. I finally dumped the contents of document.body.innerHTML after the render() call and discovered that on the second pass, the object I requested had not been rendered.

In this case, Lit is being excessively aggressive about caching what it "thinks" is in document.body, and is refusing to re-render the exact same object a second time under the same circumstances.

To undo Lit's "magic," you have to delete Lit's caching entirely, resetting the session for Lit as much as for the test harness. To do this, change the beforeEach() call to see if the lit cache key is present and delete it:

    beforeEach(async () => {
        if (document.body["_$litPart$"]) {
            delete document.body["_$litPart$"];
        }
        render(
            html`<ak-search-select-view .options=${longGoodForYouPairs} blankable>
                 </ak-search-select-view>`,
            document.body);
        select = await $("ak-search-select-view");
    });

... and that will work.

Needless to say, "resetting" your testing environment by having to remember to delete everything except Mocha's own scripts is a chore, and it's fragile. WebdriverIO offers a lot of really excellent features that not even @web/test-runner match, and it's my preferred harness, but this was a very frustrating day of discovery.

A Lit cache is installed on any parent object into which you render. If you have a particularly complex test where you have multiple objects on the page, you may have hunt them all down. I chose to render into the body, so deleting all the children and then deleting the document.body's Lit cache, was straightforward. Fragile, but straightforward.