Skip to content

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:

scripts/simple-navigation.ts
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');
});
// Run
const result = await script.run();
console.log('Success:', result.success);
console.log('Duration:', result.duration, 'ms');

Multi-Page Navigation

Navigate through multiple pages in sequence:

scripts/multi-page-navigation.ts
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:

scripts/navigation-events.ts
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:

scripts/history-navigation.ts
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:

scripts/redirect-handling.ts
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:

scripts/tab-management.ts
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

Terminal window
npx tsx scripts/simple-navigation.ts

Key Takeaways

  1. Always wait for load states after navigation
  2. Use waitForURL for redirects and SPA navigation
  3. Handle popups with waitForEvent('popup')
  4. Track navigation history in script data for debugging
  5. Use checkpoints to group related navigation steps

Next Steps