top | item 39361202

(no title)

kolodny | 2 years ago

Author here. Can you show an example of how Playwright would progress a timer on a page? For example, how would you make this component pass faster than 60 seconds?

    export const Countdown = () => {
      const [time, setTime] = React.useState(60);
      React.useEffect(() => {
        const interval = setInterval(() => {
          setTime(t => t - 1);
          if (t === 1) clearInterval(interval);
        }, 1000);

        return () => clearInterval(interval)
      }, []);

      return time > 0 ? <>Time left {time}</> : <>Done</>
    };
Short of using Playwright Component Tests, this isn't possible.

I also found an issue where the only solution was to inject sinon into the page which I wouldn't consider a great option: https://github.com/microsoft/playwright/issues/10230

discuss

order

hmeh|2 years ago

I'm afraid the answer to this doesn't actually lie in tooling. It lies in software design. If something needs to be controlled, it needs to be controllable. Typically this means push. In a React component, this means props. It could be an optional prop, but once that prop was there, this component could be controlled. Once the component could be controlled via push, the page rendering the component could also be controlled via push. How do you push to a page? Query string params is the most straightforward.

So, imagine a page that rendered a version of this component that a human could navigate to (this is what was historically called a test fixture before Rails rewrote the meaning of this word), then imagine that that human could have complete control over this interval by setting a query string argument. A human can do all of the interactive testing they need. Then, when it comes time to automate, all we need to do is automate what the human can already do.

This is another principle of automation that has been lost in history. We should first be able to do things manually before automating them. When we (as automaters) jump right to automation we often simultaneously necessitate and embrace (again, because we identify as automaters) additional complexity in the form of tooling.

I'd venture a guess that SafeTest is not likely to be necessary for the things that it was built for. Software design could have solved the problems with significantly less complexity and tooling while simultaneously providing useful test fixtures for humans to explore.

Storybook kind of enables, but it's also tooling fixation in my opinion. That's another post, however.

Oh, and I saw your other post about rewriting components to allow testability. You may be attempted to accuse me of suggesting that here. I'm not. I'm suggesting that components are written with fundamental design principles in mind, especially the necessity to exert control.

There's more to say about this that touches on the example of the sign in, and I can expand if interested.

sibeliuss|2 years ago

Need to print this and hang it on my wall! Exactly.

drewcoo|2 years ago

If you want to do it with the whole page and talk only to the local code, then yes, I'd recommend Sinon. I think that's a much simpler solution than . . . creating an all new NIH framework!

I'd also recommend refactoring to a more mock-friendly way to do that countdown if you don't want to cover up all the internal logic.

If the timeout interval is loaded remotely from some API (and it probably is if you have reasonably configurable nag popups), then you can always mock that API call.

kolodny|2 years ago

The point is that you shouldn't need to rewrite your countdown component to allow testing. Can you provide a snippet of that change and what the test would look like?

Not being toggle parts of the app is the root of the issue when creating e2e tests. For example overriding a feature flag, you could find the API call for the feature but what if it's a grpc call and part of your dev build pulls in any update, you can't easily change the binary response with confidence anymore.

The current solutions are good enough to do smoke tests, but nitty-gritty e2e tests don't scale well.

In the example above it's simple

    export const CountdownTime = createOverride(60);
    export const Countdown = () => {
      const initialTime = CountdownTime.useValue();
      const [time, setTime] = React.useState(initialTime);
      React.useEffect(() => {
        const interval = setInterval(() => {
          setTime(t => t - 1);
          if (t === 1) clearInterval(interval);
        }, 1000);

        return () => clearInterval(interval)
      }, []);

      return time > 0 ? <>Time left {time}</> : <>Done</>
    };

    it('tests faster', async () => {
      const { page } = await render(
        app => <CountdownTime.Override with={() => 5}>{app}</CountdownTime.Override>
      );
      await expect(page.getByText('Done')).not.toBeVisible();
      page.waitForTimeout(5000);
      await expect(page.getByText('Done')).toBeVisible();
    });

If I needed something like this, I'd probably also make setInterval an override as well, so I don't need to wait at all, but you get the idea.