Skip to content

Commit 04fa78a

Browse files
committed
feat: Extend navigate command with browser actions (refresh, back, forward)
- Updated handler to support new actions alongside URL navigation. - Enhanced flags documentation to reflect the additional options. - Added comprehensive test coverage for the new browser actions.
1 parent 859d8cf commit 04fa78a

4 files changed

Lines changed: 97 additions & 21 deletions

File tree

skills/wdiox-usage/flags.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,13 @@ export const config = {
7070

7171
## `navigate` / `goto` flags
7272

73-
| Positional / Flag | Notes |
74-
|-------------------|------------------------------------------|
75-
| `<url>` | URL to navigate to (required) |
76-
| `--session` / `-s`| Session name (default: `default`) |
73+
| Positional / Flag | Notes |
74+
|-------------------|----------------------------------------------------------------|
75+
| `<url>` | URL to navigate to |
76+
| `refresh` | Reload the current page (`wdiox navigate refresh`) |
77+
| `back` | Go back in browser history (`wdiox navigate back`) |
78+
| `forward` | Go forward in browser history (`wdiox navigate forward`) |
79+
| `--session` / `-s`| Session name (default: `default`) |
7780

7881
## `scroll` / `swipe` flags
7982

skills/wdiox-usage/navigate-scroll-steps.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
11
# `navigate`, `scroll`, and `steps` — Guide
22

3-
## `wdiox navigate <url>` / `goto <url>`
3+
## `wdiox navigate <url|action>` / `goto <url>`
44

5-
Changes the URL of the active session without closing and reopening it. Works on both browser and mobile (deep link / intent).
5+
Changes the URL of the active session, or controls browser history. Works on both browser and mobile (deep link / intent).
66

77
```bash
88
wdiox navigate https://example.com/dashboard
99
wdiox goto https://example.com/settings # alias
10+
11+
# Browser history actions
12+
wdiox navigate refresh # reload the current page
13+
wdiox navigate back # go back in browser history
14+
wdiox navigate forward # go forward in browser history
1015
```
1116

1217
**When to use over `open`:**
1318
- Mid-session page changes (after login, after clicking a link that fails, etc.)
1419
- Skipping intermediate pages in a multi-step flow to resume from a known URL
1520
- Mobile deep links (if the app registers the URL scheme)
1621

17-
**Mobile note:** `navigate` on a native Appium session sends a URL intent via ADB. It works for apps that register URL schemes (deep links). For navigating within the app, use `click` on nav elements instead.
22+
**Mobile note:** `navigate` on a native Appium session sends a URL intent via ADB. It works for apps that register URL schemes (deep links). For navigating within the app, use `click` on nav elements instead. History actions (`refresh`, `back`, `forward`) are browser-only.
1823

1924
```bash
2025
# Browser: skip login, start from the dashboard
2126
wdiox open https://app.example.com/login
2227
wdiox fill e2 user@example.com && wdiox fill e3 "$PASSWORD" && wdiox click e4
2328
wdiox navigate https://app.example.com/dashboard # jump straight to target
2429
wdiox snapshot
30+
31+
# Reload after a form submission to check the result
32+
wdiox click e3 && wdiox navigate refresh && wdiox snapshot
2533
```
2634

2735
---

src/commands/navigate.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,52 @@ import { attach } from 'webdriverio';
44
import { buildAttachOptions, withSession } from '../session.js';
55
import { appendStep } from '../steps.js';
66

7-
export const command = ['navigate <url>', 'goto <url>'];
8-
export const desc = 'Navigate to a URL in the active session';
7+
export const command = ['navigate [url]', 'goto [url]'];
8+
export const desc = 'Navigate to a URL, or run refresh / back / forward';
9+
10+
const BROWSER_ACTIONS = ['refresh', 'back', 'forward'] as const;
11+
type BrowserAction = typeof BROWSER_ACTIONS[number];
912

1013
export const builder = (yargs: Argv) => {
1114
return yargs.positional('url', {
1215
type: 'string',
13-
describe: 'URL to navigate to',
16+
describe: 'URL to navigate to, or: refresh | back | forward',
1417
});
1518
};
1619

1720
interface NavigateArgs {
18-
url: string
21+
url?: string;
1922
session: string
2023
_sessionsDir?: string
2124
}
2225

2326
export const handler = withSession<NavigateArgs>(async (argv: ArgumentsCamelCase<NavigateArgs>, meta, sessionsDir) => {
2427
const startTime = Date.now();
25-
const sessionName = argv.session as string;
26-
const url = argv.url as string;
28+
const session = argv.session;
29+
const url = argv.url;
30+
31+
if (!url) {
32+
console.error('URL or action required. Usage: wdiox navigate <url|refresh|back|forward>');
33+
return;
34+
}
2735

2836
const browser = await attach(buildAttachOptions(meta));
37+
const action = BROWSER_ACTIONS.includes(url as BrowserAction) ? url as BrowserAction : null;
2938

3039
try {
31-
await browser.url(url);
32-
await appendStep(sessionName, 'navigate', { url }, 'ok', Date.now() - startTime, undefined, sessionsDir);
33-
console.log(`Navigated to ${url}`);
40+
if (action) {
41+
await browser[action](); // This calls browser.refresh() or browser.back() or browser.forward() respectively
42+
await appendStep(session, action, {}, 'ok', Date.now() - startTime, undefined, sessionsDir);
43+
console.log(`Browser: ${action}`);
44+
} else {
45+
await browser.url(url);
46+
await appendStep(session, 'navigate', { url }, 'ok', Date.now() - startTime, undefined, sessionsDir);
47+
console.log(`Navigated to [${url}]`);
48+
}
49+
3450
} catch (err) {
3551
const msg = err instanceof Error ? err.message : String(err);
36-
await appendStep(sessionName, 'navigate', { url }, 'error', Date.now() - startTime, msg, sessionsDir);
37-
console.error(`Error navigating to ${url}: ${msg}`);
52+
await appendStep(session, action ?? 'navigate', action ? {} : { url }, 'error', Date.now() - startTime, msg, sessionsDir);
53+
console.error(`Error: ${msg}`);
3854
}
3955
});

tests/commands/navigate.test.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@ import fs from 'node:fs/promises';
33
import path from 'node:path';
44
import os from 'node:os';
55

6-
const { mockUrl } = vi.hoisted(() => ({
6+
const { mockUrl, mockRefresh, mockBack, mockForward } = vi.hoisted(() => ({
77
mockUrl: vi.fn(),
8+
mockRefresh: vi.fn(),
9+
mockBack: vi.fn(),
10+
mockForward: vi.fn(),
811
}));
912

1013
vi.mock('webdriverio', () => ({
11-
attach: vi.fn().mockResolvedValue({ url: mockUrl }),
14+
attach: vi.fn().mockResolvedValue({
15+
url: mockUrl,
16+
refresh: mockRefresh,
17+
back: mockBack,
18+
forward: mockForward,
19+
}),
1220
}));
1321
vi.mock('../../src/steps.js', () => ({
1422
appendStep: vi.fn().mockResolvedValue(undefined),
@@ -37,6 +45,9 @@ describe('navigate command', () => {
3745
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
3846
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
3947
mockUrl.mockClear();
48+
mockRefresh.mockClear();
49+
mockBack.mockClear();
50+
mockForward.mockClear();
4051
});
4152

4253
afterEach(async () => {
@@ -52,6 +63,30 @@ describe('navigate command', () => {
5263
expect(appendStep).toHaveBeenCalledWith('default', 'navigate', { url: 'https://httpbin.org' }, 'ok', expect.any(Number), undefined, expect.any(String));
5364
});
5465

66+
it('should refresh the page', async () => {
67+
await handler({ url: 'refresh', session: 'default', _sessionsDir: TEST_DIR } as unknown as Parameters<typeof handler>[0]);
68+
expect(mockRefresh).toHaveBeenCalled();
69+
expect(mockUrl).not.toHaveBeenCalled();
70+
expect(logSpy).toHaveBeenCalledWith('Browser: refresh');
71+
expect(appendStep).toHaveBeenCalledWith('default', 'refresh', {}, 'ok', expect.any(Number), undefined, expect.any(String));
72+
});
73+
74+
it('should go back', async () => {
75+
await handler({ url: 'back', session: 'default', _sessionsDir: TEST_DIR } as unknown as Parameters<typeof handler>[0]);
76+
expect(mockBack).toHaveBeenCalled();
77+
expect(mockUrl).not.toHaveBeenCalled();
78+
expect(logSpy).toHaveBeenCalledWith('Browser: back');
79+
expect(appendStep).toHaveBeenCalledWith('default', 'back', {}, 'ok', expect.any(Number), undefined, expect.any(String));
80+
});
81+
82+
it('should go forward', async () => {
83+
await handler({ url: 'forward', session: 'default', _sessionsDir: TEST_DIR } as unknown as Parameters<typeof handler>[0]);
84+
expect(mockForward).toHaveBeenCalled();
85+
expect(mockUrl).not.toHaveBeenCalled();
86+
expect(logSpy).toHaveBeenCalledWith('Browser: forward');
87+
expect(appendStep).toHaveBeenCalledWith('default', 'forward', {}, 'ok', expect.any(Number), undefined, expect.any(String));
88+
});
89+
5590
it('should record error step when navigation throws', async () => {
5691
mockUrl.mockRejectedValueOnce(new Error('Navigation failed'));
5792
await handler({ url: 'https://httpbin.org', session: 'default', _sessionsDir: TEST_DIR } as unknown as Parameters<typeof handler>[0]);
@@ -61,9 +96,23 @@ describe('navigate command', () => {
6196
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Navigation failed'));
6297
});
6398

99+
it('should record error step when refresh throws', async () => {
100+
mockRefresh.mockRejectedValueOnce(new Error('Refresh failed'));
101+
await handler({ url: 'refresh', session: 'default', _sessionsDir: TEST_DIR } as unknown as Parameters<typeof handler>[0]);
102+
expect(appendStep).toHaveBeenCalledWith(
103+
'default', 'refresh', {}, 'error', expect.any(Number), 'Refresh failed', expect.any(String),
104+
);
105+
});
106+
107+
it('should error when no url provided', async () => {
108+
await handler({ url: undefined, session: 'default', _sessionsDir: TEST_DIR } as unknown as Parameters<typeof handler>[0]);
109+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('URL or action required'));
110+
expect(mockUrl).not.toHaveBeenCalled();
111+
});
112+
64113
it('should error when no session exists', async () => {
65114
await handler({ url: 'https://httpbin.org', session: 'nonexistent', _sessionsDir: TEST_DIR } as unknown as Parameters<typeof handler>[0]);
66115
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('No active session'));
67116
expect(mockUrl).not.toHaveBeenCalled();
68117
});
69-
});
118+
});

0 commit comments

Comments
 (0)