Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions e2e/davinci-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
<a href="https://www.typescriptlang.org/" target="_blank">
<img src="./public/typescript.svg" class="logo vanilla" alt="TypeScript logo" />
</a>
<form id="form">
<div class="error-div"></div>
</form>
<form id="form"></form>
</div>
</div>
<script type="module" src="main.ts"></script>
Expand Down
10 changes: 5 additions & 5 deletions e2e/davinci-app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ const urlParams = new URLSearchParams(window.location.search);
formEl.innerHTML = '';

const clientInfo = davinciClient.getClient();
//const clientInfo = node.client;

let formName = '';

Expand All @@ -191,11 +190,12 @@ const urlParams = new URLSearchParams(window.location.search);

const error = davinciClient.getError();
if (error) {
formEl.appendChild(document.createElement('div')).setAttribute('id', 'error-div');
const errorDiv = formEl.querySelector('#error-div');
if (errorDiv && clientInfo?.status === 'continue') {
const errorDiv = document.createElement('div');
formEl.appendChild(errorDiv).setAttribute('id', 'error-div');
if (errorDiv && clientInfo?.status === 'error') {
errorDiv.style.color = 'red';
errorDiv.innerHTML = `
<div>${davinciClient.getError()?.message}</div>
<p><strong>Error</strong>: ${davinciClient.getError()?.message}</p>
`;
}
Comment thread
cerebrl marked this conversation as resolved.
}
Expand Down
12 changes: 12 additions & 0 deletions e2e/davinci-app/server-configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,16 @@ export const serverConfigs: Record<string, DaVinciConfig> = {
'https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/.well-known/openid-configuration',
},
},
/**
* Polling
*/
'31a587ce-9aa4-4f36-a09f-78cd8a0a74a0': {
clientId: '31a587ce-9aa4-4f36-a09f-78cd8a0a74a0',
redirectUri: window.location.origin + '/',
scope: 'openid profile email revoke',
serverConfig: {
wellknown:
'https://auth.pingone.ca/356a254c-cba3-4ade-be1a-860136e8df01/as/.well-known/openid-configuration',
},
},
};
4 changes: 4 additions & 0 deletions e2e/davinci-app/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ input {
color: #888;
}

.error {
color: red;
}

button {
border-radius: 8px;
border: 1px solid transparent;
Expand Down
178 changes: 178 additions & 0 deletions e2e/davinci-suites/src/polling.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
import { expect, test } from '@playwright/test';
import { asyncEvents } from './utils/async-events.js';

test.describe('Challenge Polling', () => {
test('should succeed when opening magic link', async ({ page, browser }) => {
const clientId = '31a587ce-9aa4-4f36-a09f-78cd8a0a74a0';
const davinciPolicy = 'f40b544a4dfb575daa0cf5e9487c206a';
const { navigate } = asyncEvents(page);
await navigate(`/?clientId=${clientId}&acr_values=${davinciPolicy}`);

await expect(page.url()).toBe(
`http://localhost:5829/?clientId=${clientId}&acr_values=${davinciPolicy}`,
);

await page.getByRole('button', { name: 'Sign On' }).click();
await expect(page.getByRole('heading', { name: 'Polling' })).toBeVisible();

// Get magic link
const linkLocator = page.getByText('Number Challenge https://auth.pingone');
await expect(linkLocator).toBeVisible();

const linkLocatorText = await linkLocator.innerText();
const magicLink = linkLocatorText.split('Number Challenge ')[1];
expect(magicLink).toContain('https://auth.pingone');

// Start polling
await page.getByRole('button', { name: 'Start polling' }).click();
await expect(page.getByText('Polling...')).toBeVisible();

// Go to magic link in another browser to complete challenge
const newContext = await browser.newContext();
const newPage = await newContext.newPage();
await newPage.goto(magicLink);
await expect(newPage.getByText('Close me')).toBeVisible();
await newContext.close();

// Check for success
await expect(page.getByText('Message: approved')).toBeVisible();
});

test('should timeout when retries are exhausted', async ({ page }) => {
const clientId = '31a587ce-9aa4-4f36-a09f-78cd8a0a74a0';
const davinciPolicy = 'f40b544a4dfb575daa0cf5e9487c206a';
const { navigate } = asyncEvents(page);
await navigate(`/?clientId=${clientId}&acr_values=${davinciPolicy}`);

await expect(page.url()).toBe(
`http://localhost:5829/?clientId=${clientId}&acr_values=${davinciPolicy}`,
);

await page.getByRole('button', { name: 'Sign On' }).click();
await expect(page.getByRole('heading', { name: 'Polling' })).toBeVisible();

// Track poll retries
let numPollRequests = 0;
page.on('request', (request) => {
const method = request.method();
const requestUrl = request.url();

if (method === 'POST' && requestUrl.includes('/status')) {
numPollRequests++;
}
});

// Start polling
await page.getByRole('button', { name: 'Start polling' }).click();
await expect(page.getByText('Polling...')).toBeVisible();

// Wait for timeout
const pollInterval = 2000; // milliseconds
const maxRetries = 5;
await expect(page.getByText('Error: timedOut')).toBeVisible({
timeout: 2 * pollInterval * maxRetries,
});

// Check max retry count
expect(numPollRequests).toBe(maxRetries);
});

test('should return expired status after challenge expires', async ({ page }) => {
const clientId = '31a587ce-9aa4-4f36-a09f-78cd8a0a74a0';
const davinciPolicy = 'f40b544a4dfb575daa0cf5e9487c206a';
const { navigate } = asyncEvents(page);
await navigate(`/?clientId=${clientId}&acr_values=${davinciPolicy}`);

await expect(page.url()).toBe(
`http://localhost:5829/?clientId=${clientId}&acr_values=${davinciPolicy}`,
);

await page.getByRole('button', { name: 'Sign On' }).click();
await expect(page.getByRole('heading', { name: 'Polling' })).toBeVisible();

// Track poll retries
let numPollRequests = 0;
page.on('request', (request) => {
const method = request.method();
const requestUrl = request.url();

if (method === 'POST' && requestUrl.includes('/status')) {
numPollRequests++;
}
});

// Wait for challenge to expire
const challengeExpiry = 15000; // milliseconds
await page.waitForTimeout(challengeExpiry + 5000);

// Start polling
await page.getByRole('button', { name: 'Start polling' }).click();
await expect(page.getByText('Polling...')).toBeVisible();

// Check for expired status
await expect(page.getByText('Error: expired')).toBeVisible();

// Check poll count
expect(numPollRequests).toBe(1);
});
});

test.describe('Continue Polling', () => {
test('should succeed on QR code scan simulation', async ({ page }) => {
const clientId = '31a587ce-9aa4-4f36-a09f-78cd8a0a74a0';
const davinciPolicy = '27aacf0efcc480dfcd00b04be8023cdc';
const { navigate } = asyncEvents(page);
await navigate(`/?clientId=${clientId}&acr_values=${davinciPolicy}`);

await expect(page.url()).toBe(
`http://localhost:5829/?clientId=${clientId}&acr_values=${davinciPolicy}`,
);

await expect(page.getByRole('heading', { name: 'Select Continue Polling Test' })).toBeVisible();
await page.getByRole('button', { name: 'Success' }).click();
await expect(page.getByRole('heading', { name: 'Polling' })).toBeVisible();

// Start polling
const numberCounterSuccess = 2;
for (let i = 0; i < numberCounterSuccess; i++) {
await page.getByRole('button', { name: 'Start polling' }).click();
await expect(page.getByText('Polling...')).toBeVisible();
await expect(page.getByRole('button', { name: 'Start polling' })).toBeDisabled();
}

// Check for success
await expect(page.getByText('Message: Done')).toBeVisible();
});

test('should timeout when retries are exhausted', async ({ page }) => {
const clientId = '31a587ce-9aa4-4f36-a09f-78cd8a0a74a0';
const davinciPolicy = '27aacf0efcc480dfcd00b04be8023cdc';
const { navigate } = asyncEvents(page);
await navigate(`/?clientId=${clientId}&acr_values=${davinciPolicy}`);

await expect(page.url()).toBe(
`http://localhost:5829/?clientId=${clientId}&acr_values=${davinciPolicy}`,
);

await expect(page.getByRole('heading', { name: 'Select Continue Polling Test' })).toBeVisible();
await page.getByRole('button', { name: 'Timeout' }).click();
await expect(page.getByRole('heading', { name: 'Polling' })).toBeVisible();

// Start polling
const maxRetries = 3;
for (let i = 0; i < maxRetries + 1; i++) {
await page.getByRole('button', { name: 'Start polling' }).click();
await expect(page.getByText('Polling...')).toBeVisible();
await expect(page.getByRole('button', { name: 'Start polling' })).toBeDisabled();
}

// Check for timeout
await expect(page.getByText('Error: timedOut')).toBeVisible();
});
});
4 changes: 2 additions & 2 deletions e2e/davinci-suites/src/protect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ test('Test Protect collector with Custom HTML component', async ({ page }) => {

await expect(page.getByText('JS Protect - Custom HTML Form')).toBeVisible();

const requests = [];
const requests: string[] = [];
page.on('request', (request) => {
const method = request.method();
const requestUrl = request.url();
Expand Down Expand Up @@ -54,7 +54,7 @@ test('Test Protect collector with P1 Forms component', async ({ page }) => {

await expect(page.getByText('Example - Sign On')).toBeVisible();

const requests = [];
const requests: string[] = [];
page.on('request', (request) => {
const method = request.method();
const requestUrl = request.url();
Expand Down
7 changes: 3 additions & 4 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ pre-commit:
nx-check:
run: pnpm nx affected -t typecheck lint build api-report --tui=false
stage_fixed: true
format:
run: pnpm nx format:write
stage_fixed: true
interface-mapping:
glob: >-
{tools/interface-mapping-validator/**/*.ts,
Expand All @@ -24,7 +21,9 @@ pre-commit:
echo "Interface mapping is out of sync." &&
echo "Run: pnpm mapping:generate" &&
exit 1)

format:
run: pnpm nx format:write
stage_fixed: true
commit-msg:
commands:
commitlint:
Expand Down
Loading