← All posts
Guide

Your date picker is a day off. Test the time zone.

The calendar check in four steps

  1. Fix the browser instant before the form opens: 2026-10-16T00:30Z.
  2. Check the named zone: browser-local today is October 15 in Los Angeles and October 16 in Tokyo.
  3. Choose October 16 as the date-only due date, regardless of the default.
  4. Save and reload: the saved due date must still be October 16. Browser clock control does not change server time.

You select October 16 as an invoice's due date. Save. Reopen. October 15.

The application has invented a small time machine. Unfortunately, it only travels far enough to make a payment late.

This is a useful regression to catch before a user does. A date control can display the wrong “today,” or turn a perfectly good calendar date into a timestamp and shift it during formatting. Both bugs can disappear on a developer's machine and return in another time zone.

This guide gives you two focused checks: does the form choose the right local day, and does the chosen calendar date survive saving? The examples use a fictional invoice app and ordinary Playwright Test. They do not assume a TestVibe clock-setting UI or automatic time-zone coverage.

Decide what the date means first

“The correct date” needs a policy. Is this a calendar date, an instant, or a deadline in a business's time zone?

ValueExample contractWhat to assert
Invoice due dateOctober 16, regardless of where the reader isThe same year, month, and day after save and reload
Event startOne instant, displayed in the viewer's time zoneThe expected local date and time for that instant
Order cutoffMidnight in the warehouse's named time zoneAcceptance before the cutoff and rejection at or after it

Our example invoice app defaults Due date to today in the browser's time zone. Once selected, the due date is a date-only value: 2026-10-16. Your product might use the account's or company's zone instead. Write that down and change the expectations accordingly.

A native input type="date" has a normalized YYYY-MM-DD value even when its visible presentation follows the browser locale. Assert that value when testing the selected calendar date. Test the presentation separately if its formatting is part of your contract.

Put one instant on opposite sides of midnight

Running the same test at noon in two zones may prove very little. Choose a fixed instant that makes their local dates disagree.

At 2026-10-16T00:30:00Z, these are the expected dates for our browser-local today policy:

Browser time zoneExpected Due date
UTC2026-10-16
America/Los_Angeles2026-10-15
Asia/Tokyo2026-10-16

The Los Angeles row earns its place: code that takes the date portion of a UTC timestamp will choose October 16 there and fail the check. Another useful instant is 2026-10-16T23:30:00Z; Tokyo should then choose October 17. You want disagreement in the inputs, not three reassuring copies of the same day.

Keep the expected values literal. If the test computes “today” with the same conversion helper as the app, both can make the same mistake and congratulate each other. The flakiness guide recommends controlling time and locale; this matrix makes that advice testable.

Fix the browser clock before the form loads

Install Playwright Test and its browser in your own test project:

npm install --save-dev @playwright/test
npx playwright install chromium

Save this as tests/date-boundaries.spec.ts. Set APP_BASE_URL to your authorized test environment. The example assumes /invoices/new exposes a native date input labeled Due date and defaults it in browser JavaScript. Adapt the route and label; arrange any dedicated account authentication through your normal Playwright setup.

import { test, expect } from '@playwright/test';

const baseURL = process.env.APP_BASE_URL;
if (!baseURL) throw new Error('Set APP_BASE_URL to your test app');

const cases = [
  ['UTC', '2026-10-16T00:30:00Z', '2026-10-16'],
  ['America/Los_Angeles', '2026-10-16T00:30:00Z', '2026-10-15'],
  ['Asia/Tokyo', '2026-10-16T00:30:00Z', '2026-10-16'],
  ['Asia/Tokyo', '2026-10-16T23:30:00Z', '2026-10-17'],
];

for (const [timezoneId, instant, expectedDate] of cases) {
  test.describe(`${timezoneId} at ${instant}`, () => {
    test.use({ timezoneId, locale: 'en-US' });

    test('defaults to the browser-local calendar day', async ({ page }) => {
      await page.clock.setFixedTime(new Date(instant));
      await page.goto(new URL('/invoices/new', baseURL).href);
      await expect(page.getByLabel('Due date', { exact: true }))
        .toHaveValue(expectedDate);
    });
  });
}

Run it with npx playwright test tests/date-boundaries.spec.ts --workers=1. The fixed timestamps include Z, so the runner does not interpret them as its own local wall time.

Playwright's clock guide documents setFixedTime: date reads use the chosen time while timers keep running. Set it before navigation so the form's initial default sees that time. This is enough for a form that reads “today” when it opens; it does not exercise a page remaining open across midnight.

Time-zone and locale emulation affect the browser, not the Node.js test runner. They also do not set an account preference stored on your server. Keep locale fixed while investigating the zone; vary formatting in a separate small matrix when needed.

Save a calendar date and read it back

The default can be correct while persistence is wrong. Add a separate journey in a western time zone: choose a known day, save a disposable invoice, then reload its saved edit page and check the same field.

Append this block to the same test file. Its synthetic customer, Save draft action, and /invoices/<id>/edit route must exist in your test app. Complete any other required fields and arrange cleanup through your fixture setup. These names are examples, not TestVibe endpoints.

test.describe('date-only round trip', () => {
  test.use({ timezoneId: 'America/Los_Angeles', locale: 'en-US' });

  test('keeps October 16 after saving and reloading', async ({ page }) => {
    await page.clock.setFixedTime(new Date('2026-10-16T00:30:00Z'));
    await page.goto(new URL('/invoices/new', baseURL).href);
    await page.getByLabel('Customer', { exact: true }).fill('Date fixture');
    await page.getByLabel('Due date', { exact: true }).fill('2026-10-16');
    await page.getByRole('button', { name: 'Save draft', exact: true }).click();

    // This example app navigates only after the draft has been saved.
    await expect(page).toHaveURL(/\/invoices\/[^/]+\/edit$/);
    await page.reload();
    await expect(page.getByLabel('Due date', { exact: true }))
      .toHaveValue('2026-10-16');
  });
});

The reload matters. A success toast and an unchanged input can both come from unsaved client state. Read the value from the saved record's screen. If your app preserves unsaved fields across reloads, use a fresh page or browser context to read the record, and check the server's stored value through your supported test API as well. Follow the create, verify, delete guide for ownership and cleanup; avoid parallel tests editing one shared invoice.

One conversion worth investigating when this fails is:

new Date('2026-10-16').toLocaleDateString('en-US');

JavaScript interprets that date-only string as UTC midnight. Local formatting west of UTC can therefore show October 15. The JavaScript Date reference explains that parsing rule. A due date did not need an instant in the first place. Inspect the request, stored value, and rendering path to find where the application introduced one.

Do not “fix” the assertion by accepting either day. The policy has one answer.

Keep browser time separate from server time

A frozen browser clock does not move your database, backend, payment provider, or scheduled job into the future. If the server decides whether an invoice is overdue, changing the browser's date alone cannot prove that rule.

For a server-owned deadline, arrange controlled records and a supported clock seam in an isolated test environment, or test the boundary in the service's own test suite. Then cover how the browser presents the server's decision. A mocked response can check that presentation, but it cannot prove that the real service calculated the cutoff correctly.

Elapsed time is another question. If a page must refresh at midnight, use Playwright's clock.install, pauseAt, and runFor to control timers and cross the boundary deliberately. Install before navigation and let startup complete before pausing. Choose runFor when each scheduled callback matters; fastForward fires due timers at most once. Keep these timer scenarios separate from the fixed-date examples above.

For calendar arithmetic, add cases around the named zone's daylight-saving transitions if your app supports them. “Next calendar day” and “24 hours later” are different contracts around an offset change. Use transition fixtures appropriate to your supported zones and runtime data; this four-row matrix does not establish worldwide date correctness.

Bring the expectation into TestVibe

TestVibe's authoring docs support saved Gherkin or Markdown intent, and the run docs distinguish generated Playwright runs from Assistant runs. Record the date policy, controlled starting state, labels, and exact expected result in that intent.

For example: “In the prepared test environment, open the saved Date fixture invoice. Its due date is October 16, 2026. Reload the saved invoice and confirm Due date is still October 16, 2026.” Name how the environment and fixture were prepared; the sentence itself does not set a clock or time zone.

Keep the executable clock matrix in your code-owned Playwright suite unless you have verified the required configuration and clock calls in your TestVibe runner. Review generated code and completed evidence. An Assistant interpreting an instruction and saved Playwright executing explicit clock calls are different execution paths; a pass in one does not verify the other.

Start with one date field that matters to your users. Give “today” an explicit zone, test an instant near midnight, and make a saved calendar date survive a reload. That is a small regression with a clear answer, even when the CI machine lives somewhere else.

Coming soon

Turn a described flow into a reviewable test.

Describe a flow in plain English. Generate a Playwright test, run it on your schedule, and review proposed fixes when your UI changes. Join the waitlist for early access.

  • Plain English in, real Playwright out
  • Runs in your CI, on your schedule
  • No credit card, unsubscribe anytime