Basic Navigation
Basic Navigation
This example demonstrates basic page navigation patterns with Stepwright.
Simple Page Visit
The most basic script navigates to a page and verifies it loaded:
import { Stepwright } from '@korvol/stepwright';
const script = Stepwright.create('Simple Navigation') .config({ headless: true, screenshotOnFailure: true, })
.step('Navigate to homepage', async (ctx) => { await ctx.page.goto('https://example.com'); ctx.log('Page loaded:', ctx.page.url()); })
.step('Verify page title', async (ctx) => { const title = await ctx.page.title(); ctx.log('Page title:', title);
if (!title.includes('Example')) { throw new Error(`Unexpected title: ${title}`); } })
.step('Take screenshot', async (ctx) => { await ctx.screenshot('homepage'); });
// Runconst result = await script.run();console.log('Success:', result.success);console.log('Duration:', result.duration, 'ms');Multi-Page Navigation
Navigate through multiple pages in sequence:
import { Stepwright } from '@korvol/stepwright';
interface NavigationData { visitedPages: string[]; pageTitles: Record<string, string>;}
const script = Stepwright.create<NavigationData>('Multi-Page Navigation') .config({ headless: true, defaultTimeout: 30000, }) .data({ visitedPages: [], pageTitles: {}, })
.checkpoint('Homepage')
.step('Visit homepage', async (ctx) => { await ctx.page.goto('https://example.com'); await ctx.page.waitForLoadState('domcontentloaded');
const url = ctx.page.url(); const title = await ctx.page.title();
ctx.data.visitedPages.push(url); ctx.data.pageTitles[url] = title;
ctx.log('Visited:', url); })
.endCheckpoint()
.checkpoint('About Page')
.step('Navigate to about page', async (ctx) => { // Click navigation link await ctx.page.click('a[href*="about"]'); await ctx.page.waitForLoadState('networkidle');
const url = ctx.page.url(); const title = await ctx.page.title();
ctx.data.visitedPages.push(url); ctx.data.pageTitles[url] = title;
ctx.log('Navigated to:', url); })
.endCheckpoint()
.checkpoint('Contact Page')
.step('Navigate to contact page', async (ctx) => { await ctx.page.click('a[href*="contact"]'); await ctx.page.waitForLoadState('networkidle');
const url = ctx.page.url(); const title = await ctx.page.title();
ctx.data.visitedPages.push(url); ctx.data.pageTitles[url] = title;
ctx.log('Navigated to:', url); })
.endCheckpoint()
.step('Summary', async (ctx) => { ctx.log('Pages visited:', ctx.data.visitedPages.length); for (const [url, title] of Object.entries(ctx.data.pageTitles)) { ctx.log(` ${title}: ${url}`); } });
const result = await script.run();
if (result.success) { console.log('Visited pages:', result.data.visitedPages);}Handling Navigation Events
Handle different navigation scenarios:
import { Stepwright } from '@korvol/stepwright';
const script = Stepwright.create('Navigation Events') .config({ headless: true, })
.step('Navigate with wait for URL', async (ctx) => { await ctx.page.goto('https://example.com/login');
// Fill and submit form await ctx.page.fill('#username', 'user'); await ctx.page.fill('#password', 'pass'); await ctx.page.click('#submit');
// Wait for redirect to dashboard await ctx.page.waitForURL('**/dashboard/**');
ctx.log('Redirected to:', ctx.page.url()); })
.step('Handle popup navigation', async (ctx) => { // Listen for popup before triggering const popupPromise = ctx.page.waitForEvent('popup');
await ctx.page.click('a[target="_blank"]');
const popup = await popupPromise; await popup.waitForLoadState();
ctx.log('Popup URL:', popup.url());
// Close popup when done await popup.close(); })
.step('Navigate with response wait', async (ctx) => { // Wait for API call during navigation const responsePromise = ctx.page.waitForResponse( (res) => res.url().includes('/api/user') && res.status() === 200 );
await ctx.page.click('#profile-link');
const response = await responsePromise; const data = await response.json();
ctx.log('User data loaded:', data.name); });Back/Forward Navigation
Test browser history navigation:
import { Stepwright } from '@korvol/stepwright';
const script = Stepwright.create('History Navigation') .config({ headless: true, })
.step('Visit page 1', async (ctx) => { await ctx.page.goto('https://example.com/page1'); ctx.log('Page 1:', ctx.page.url()); })
.step('Visit page 2', async (ctx) => { await ctx.page.goto('https://example.com/page2'); ctx.log('Page 2:', ctx.page.url()); })
.step('Visit page 3', async (ctx) => { await ctx.page.goto('https://example.com/page3'); ctx.log('Page 3:', ctx.page.url()); })
.step('Go back', async (ctx) => { await ctx.page.goBack(); ctx.log('After back:', ctx.page.url());
// Verify we're on page 2 if (!ctx.page.url().includes('page2')) { throw new Error('Expected to be on page 2'); } })
.step('Go forward', async (ctx) => { await ctx.page.goForward(); ctx.log('After forward:', ctx.page.url());
// Verify we're on page 3 if (!ctx.page.url().includes('page3')) { throw new Error('Expected to be on page 3'); } })
.step('Go back twice', async (ctx) => { await ctx.page.goBack(); await ctx.page.goBack(); ctx.log('After two backs:', ctx.page.url());
// Verify we're on page 1 if (!ctx.page.url().includes('page1')) { throw new Error('Expected to be on page 1'); } });Handling Redirects
Handle different redirect types:
import { Stepwright } from '@korvol/stepwright';
const script = Stepwright.create('Redirect Handling') .config({ headless: true, defaultTimeout: 30000, })
.step('Handle server redirect', async (ctx) => { // Navigate to URL that redirects (301/302) const response = await ctx.page.goto('https://example.com/old-page');
if (response) { ctx.log('Final URL:', response.url()); ctx.log('Status:', response.status());
// Check if redirected if (response.url() !== 'https://example.com/old-page') { ctx.log('Redirected from old-page'); } } })
.step('Handle JavaScript redirect', async (ctx) => { await ctx.page.goto('https://example.com/js-redirect');
// Wait for JS redirect to complete await ctx.page.waitForURL('**/destination/**', { timeout: 10000, });
ctx.log('JS redirect complete:', ctx.page.url()); })
.step('Handle meta refresh', async (ctx) => { await ctx.page.goto('https://example.com/meta-redirect');
// Wait for meta refresh (can take time) await ctx.page.waitForURL('**/refreshed/**', { timeout: 15000, });
ctx.log('Meta refresh complete:', ctx.page.url()); });Tab/Window Management
Work with multiple tabs:
import { Stepwright } from '@korvol/stepwright';
interface TabData { mainUrl: string; newTabUrl: string;}
const script = Stepwright.create<TabData>('Tab Management') .config({ headless: true, }) .data({ mainUrl: '', newTabUrl: '', })
.step('Open main page', async (ctx) => { await ctx.page.goto('https://example.com'); ctx.data.mainUrl = ctx.page.url(); ctx.log('Main tab:', ctx.data.mainUrl); })
.step('Open new tab', async (ctx) => { const context = ctx.page.context();
// Create new page (tab) in same context const newPage = await context.newPage(); await newPage.goto('https://example.com/other');
ctx.data.newTabUrl = newPage.url(); ctx.log('New tab:', ctx.data.newTabUrl);
// Do something in new tab const title = await newPage.title(); ctx.log('New tab title:', title);
// Close the new tab await newPage.close(); })
.step('Verify original tab still works', async (ctx) => { // Original page should still be accessible const currentUrl = ctx.page.url(); ctx.log('Original tab still at:', currentUrl);
if (currentUrl !== ctx.data.mainUrl) { throw new Error('Original tab URL changed unexpectedly'); } });Running the Examples
npx tsx scripts/simple-navigation.tsnpx stepwright run scripts/simple-navigation.tsnpx stepwright run scripts/multi-page-navigation.ts --videoKey Takeaways
- Always wait for load states after navigation
- Use
waitForURLfor redirects and SPA navigation - Handle popups with
waitForEvent('popup') - Track navigation history in script data for debugging
- Use checkpoints to group related navigation steps
Next Steps
- Form Submission - Handle forms and inputs
- Authentication - Login flows
- Handling Dynamic Content - AJAX and SPAs