# One test across platforms

> Write one test that creates a task on iOS, changes it on the web and checks it on macOS.

Source: https://rehearsal.dev/retest/docs/platforms/across-platforms

Use several named apps in one test when you need to check that a change reaches another platform. This example creates a task on iOS, marks it done on the web and waits for macOS to show the update.

## Configure the apps

Give each app a name and point all three clients at the same task service. This example uses TaskPhone and TaskDesk from Retest's cross-platform fixture, with their own launch arguments and bundle identifiers.

```ts filename="retest.config.ts"
import { chrome, defineConfig, env } from '@rehearsal-labs/retest'

const config = defineConfig({
  apps: {
    phone: {
      platform: 'ios-simulator',
      appPath: './TaskPhone.app',
      device: 'iPhone 17',
      runtime: '26.5',
      arguments: ['-reset', '-serviceURL', 'http://127.0.0.1:4310'],
    },
    web: chrome({ baseUrl: 'http://127.0.0.1:4310' }),
    desk: {
      platform: 'macos',
      appPath: './TaskDesk.app',
      arguments: ['-reset', '-serviceURL', 'http://127.0.0.1:4310'],
    },
  },
  secrets: { password: env('TASK_APP_PASSWORD') },
  secretOrigins: {
    password: ['dev.retest.fixtures.taskphone', 'dev.retest.fixtures.taskdesk'],
  },
})

export default config

declare module '@rehearsal-labs/retest' {
  interface Register {
    config: typeof config
  }
}
```

The web app's base URL authorises password entry on its own origin. Native apps instead need their bundle identifiers in `secretOrigins`. Prepare the service and account, then follow the [iOS](https://rehearsal.dev/retest/docs/platforms/ios.md) and [macOS](https://rehearsal.dev/retest/docs/platforms/macos.md) setup instructions on the same Mac.

## Follow one task

Declare all three apps in the test options to receive their named handles. The example follows the task id assigned by the service because two records can share a title. Prepare the account so it has no other service-assigned tasks before running this test.

```ts filename="tests/sync.retest.ts"
import { expect, secret, test } from '@rehearsal-labs/retest'

test('syncs a completed task', { apps: ['phone', 'web', 'desk'] }, async ({ phone, web, desk }) => {
  const title = 'Release checklist'

  await phone.getByTestId('account-field').fill('ada')
  await phone.getByTestId('password-field').fill(secret('password'))
  await phone.getByTestId('sign-in-button').tap()
  await expect(phone.getByTestId('signed-in-account')).toHaveText('ada')
  await phone.getByTestId('new-task-title-field').fill(title)
  await phone.getByTestId('create-task-button').tap()
  await expect(phone.getByTestId('created-task-title')).toHaveText(title)

  await web.goto('/')
  await web.getByTestId('account').fill('ada')
  await web.getByTestId('password').fill(secret('password'))
  await web.getByTestId('sign-in').click()
  await expect(web.getByTestId('signed-in-account')).toHaveText('ada')
  await expect(web.locator('[data-testid^="task-row-task-"]')).toHaveCount(1)
  await web.locator('[data-testid^="open-task-task-"]').click()
  await expect(web).toHaveURL(/\/tasks\/task-[0-9a-f]{12}$/)
  const id = /\/tasks\/(task-[0-9a-f]{12})$/.exec(await web.url())?.[1]
  if (id === undefined) throw new Error('The address names no task.')
  await expect(phone.getByTestId('created-task-id')).toHaveText(id)

  await desk.getByTestId('account-field').fill('ada')
  await desk.getByTestId('password-field').fill(secret('password'))
  await desk.getByTestId('sign-in-button').click()
  await expect(desk.getByTestId('signed-in-account')).toHaveText('ada')
  await desk.getByTestId('task-id-field').fill(id)
  await desk.getByTestId('show-task-button').click()
  await expect(desk.getByTestId('selected-task-state')).toHaveText('Open')

  await web.getByTestId('edit-done').check()
  await web.getByTestId('save-task').click()
  await expect(web.getByTestId('selected-task-state')).toHaveText('Done')
  await expect(desk.getByTestId('selected-task-id')).toHaveText(id)
  await expect(desk.getByTestId('selected-task-state')).toHaveText('Done')
})
```

The web finds the task after it syncs, and the phone check confirms it is the record the test created. TaskDesk opens that id before the web changes it. The final check keeps reading until the desktop receives the update, so no fixed delay is needed.

These native apps expose accessibility identifiers such as `new-task-title-field`, while the web client has its own test ids. Use the identifiers each app exposes; Retest does not translate between them.

```sh
npx retest run tests/sync.retest.ts
```

## Read the result

The whole flow has one test result. Each step and screenshot names its app session, so the report shows where a failure happened. If a sync check fails, later steps stop, and the failure names the expected value and the value that app showed.

Retest acquires the test's desktop, simulator and other resources before launching any app. Each app accepts one command at a time, although commands to different apps may run together. This example awaits them in sequence to make the flow easier to follow.

## Platform limits

A test may use one macOS app and one app per simulator device type and runtime. Native handles do not expose direct text reads, so the example reads the id from the web address and checks it on native elements. Retest does not reset the task service, so the account preparation remains part of your setup.

An Electron app can also participate beside web apps. When one app has several targets, its test runs per target; when several apps have multiple targets, use explicit pairings in `runs`. [Choose target pairings](https://rehearsal.dev/retest/docs/essentials/configuration.md) shows how to declare them.

## Next steps

- [Parallel runs](https://rehearsal.dev/retest/docs/scaling-up/parallel-runs.md): Control workers and shared app resources.
- [Recordings and evidence](https://rehearsal.dev/retest/docs/guides/recordings-and-evidence.md): Keep the screens each app showed during the flow.
- [Best practices](https://rehearsal.dev/retest/docs/scaling-up/best-practices.md): Prepare data and check each outcome explicitly.
