Skip to content

Add a testing library for ui-extensions#3899

Open
kumar303 wants to merge 1 commit into2026-04-rcfrom
02-11-create_a_testing_library_for_ui-extensions_proof_of_concept_
Open

Add a testing library for ui-extensions#3899
kumar303 wants to merge 1 commit into2026-04-rcfrom
02-11-create_a_testing_library_for_ui-extensions_proof_of_concept_

Conversation

@kumar303
Copy link
Contributor

@kumar303 kumar303 commented Feb 11, 2026

This adds a new ui-extensions-tester library, built during Hack Days (internal Shopify link).

All stacks

👁️ How to review this PR

Example

Here's what it looks like so far in something like your-app/extensions/your-ui-extension/tests/Extension.test.ts:

import {expect, test, beforeEach, afterEach} from 'vitest';
import {setUpExtension} from '@shopify/ui-extensions-tester';

// This reads from ../shopify.extension.toml
const extension = getExtension('purchase.checkout.block.render');

beforeEach(() => {
  extension.setUp();
});

afterEach(() => {
  extension.tearDown();
});

test('renders an Enter button', async () => {
  await extension.render();
  const button = extension.querySelector('s-button');
  expect(button!.textContent).toEqual('Enter');
});

More complete example: checkout-basic-testing-example/tests/Checkout.test.ts


Run it like this after checking out the branch for this PR:

yarn build
pushd examples/testing/checkout-basic-testing-example
npm install
npm test

🎩

To try it in a standalone app, you have to install a pre-built package: shopify-ui-extensions-tester-v2026.4.0-rc.1.tgz. This is because /snapit doesn't work before there's at least one public release.

Add it to a package.json file like:

"@shopify/ui-extensions-tester": "file:../shopify-ui-extensions-tester-v2026.4.0-rc.1.tgz"

Copy link
Contributor Author

kumar303 commented Feb 11, 2026

@kumar303 kumar303 force-pushed the 02-11-create_a_testing_library_for_ui-extensions_proof_of_concept_ branch 24 times, most recently from 3d22f8d to 1f8f47e Compare February 12, 2026 11:53
@kumar303 kumar303 force-pushed the 02-11-create_a_testing_library_for_ui-extensions_proof_of_concept_ branch from 1f8f47e to 562a606 Compare February 13, 2026 15:11
@kumar303 kumar303 force-pushed the 02-11-create_a_testing_library_for_ui-extensions_proof_of_concept_ branch 2 times, most recently from bdf2921 to 2388be3 Compare February 24, 2026 14:11
@kumar303 kumar303 marked this pull request as ready for review February 24, 2026 14:26
@kumar303 kumar303 force-pushed the 02-11-create_a_testing_library_for_ui-extensions_proof_of_concept_ branch from 2388be3 to 4961fee Compare February 24, 2026 15:35
@@ -0,0 +1,363 @@
# 🧪 @shopify/ui-extensions-tester
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: naming. Random thoughts without much merit:

  • ui-extensions-testing-library - similar to React Testing Library
  • ui-extensions-test- shorter for no good reason

@kumar303 kumar303 force-pushed the 02-11-create_a_testing_library_for_ui-extensions_proof_of_concept_ branch 12 times, most recently from f1f74ae to a265b94 Compare February 27, 2026 11:42
@kumar303
Copy link
Contributor Author

/snapit

@kumar303 kumar303 force-pushed the 02-11-create_a_testing_library_for_ui-extensions_proof_of_concept_ branch 3 times, most recently from 088e832 to 4b27af2 Compare February 27, 2026 15:45
Copy link
Member

@vividviolet vividviolet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is amazing! I have a few questions/suggestions

Copy link
Member

@vividviolet vividviolet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks for addressing all the feedback. This is good to merge - I just have a few questions that could be addressed if needed in follow up prs

if (tomlVersion !== API_VERSION) {
throw new Error(
`api_version "${tomlVersion ?? '(not found)'}" does not match ` +
`the version supported by @shopify/ui-extensions-tester ("${API_VERSION}"). ` +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means we'd have to do do a new version of the test library for every library release even though most of the code remains the same. I do see the value in making it simple for developers though. We might want to automate this somehow

Copy link
Contributor Author

@kumar303 kumar303 Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we'd need to release ui-extensions-tester alongside ui-extensions but if no changes are needed then the version bump would happen automatically. It gets set in a build step.


/**
* Creates a mock `SubscribableSignalLike` that wraps a static value.
* The `subscribe` callback is never invoked since test values are static.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we write tests to work with changing signals then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since preact is the default, I think most extensions could be tested in a functional style. In other words, the mock shopify object won't have working signals but I don't think that matters.

To illustrate:

Let's say your extension has a button to change cart quantities and it also subscribes to line items. You wouldn't need the subscription to be fully functional. You could just test it in two steps:

// test that the button works
const mock = vi.fn().mockResolvedValue(
  createResult('applyCartLineChange');
);
extension.shopify.applyCartLineChange = mock;

await extension.render();
const button = document.body.querySelector('s-button');
fireEvent('click', button);
// Make sure the button changes the quantity
await waitFor(() => {
  expect(mock).toHaveBeenCalledWith(...);
});

...

// test that the extension renders the lines
const line = createLineItem();
// This essentially simulates a subscription update:
extension.shopify.lines = [line];

await extension.render();
const text = document.body.querySelector('s-text');
// Make sure the line's quantity was rendered:
expect(text.textContent).toContain(line.quantity);

This testing style assumes the shopify API fully works as specified -- I think that assumption is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for calling this out, Trish. I documented the suggested pattern to use for this.


/**
* Creates an isolated temp directory for a single test file.
* Each test file gets its own directory so tests can run in parallel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify why we need to do this? Tests shouldn't mutate the toml so why do we need to copy these tests to their own directory and clone the toml?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just for the internal ui-extensions-tester test suite. Developers would never use this.

It's necessary because some of the tests need to work with different TOML files to simulate different configurations. The fixtures getting copied here are just for defining an extension that can be imported and executed. The fixtures don't include a TOML file.

The first approach I tried was to write a local TOML file before each test but I started seeing flakiness. It worked better when creating a unique TOML file per test.

Copy link
Contributor

@jamesvidler jamesvidler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran into a snag with the error ReferenceError: __filename is not defined when running the tests, so I had to add the following to my vitetest.config

export default defineConfig({
  esbuild: {
    jsx: "automatic",
    jsxImportSource: "preact",
  },
  test: {
    environment: "jsdom",
//NEEDED TO ADD THIS, or a I got an error ReferenceError: __filename is not defined when running the tests
    server: {
      deps: {
        inline: ["@shopify/ui-extensions-tester"],
      },
    },
  },
});

It looks like we've patched that now, but I wasn't able to get the updated tgz package to relfect the changes.

I'm comfortable approving and we can test again once the package is publicly released and fix forward if neccessary or update the docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants