- Add Devin login to standard and simplified modes - Support credentials, model aliases, and exclusions - Display daily and weekly quotas with reset times - Validate callbacks and prevent stale login updates - Add translations, theme support, and integration tests
87 lines
5.4 KiB
JavaScript
87 lines
5.4 KiB
JavaScript
const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'playwright');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const base = process.env.DEVIN_TEST_BASE_URL || 'http://127.0.0.1:1421';
|
|
(async () => {
|
|
const browser = await chromium.launch({ channel: 'msedge', headless: true, args: ['--no-proxy-server'] });
|
|
try {
|
|
const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
|
|
page.setDefaultTimeout(10000);
|
|
const errors = [];
|
|
page.on('pageerror', (error) => errors.push(String(error)));
|
|
await page.route('**/*', route => route.request().url().startsWith(base + '/') ? route.continue() : route.abort());
|
|
const open = query => page.goto(base + '/tests/fixtures/devin.html?' + (query || ''), { waitUntil: 'domcontentloaded' });
|
|
const card = () => page.locator('.oauth-card').filter({ hasText: 'Devin OAuth' });
|
|
const click = label => card().getByRole('button', { name: label, exact: true }).click();
|
|
const calls = () => page.evaluate(() => window.devinFixture.calls);
|
|
await open();
|
|
await card().waitFor();
|
|
assert.equal(await page.locator('.oauth-card').count(), 6);
|
|
await click('Start Sign-In');
|
|
await card().getByRole('textbox').waitFor();
|
|
assert.ok((await calls()).some(call => call.cmd === 'start_oauth_login' && call.args.provider === 'devin' && call.args.browser === 'none'));
|
|
await card().getByRole('textbox').fill('http://127.0.0.1:8317/callback?state=old&code=code');
|
|
await click('Submit Callback');
|
|
await page.getByText('The callback state does not match', { exact: false }).waitFor();
|
|
assert.equal((await calls()).filter(call => call.cmd === 'submit_oauth_callback').length, 0);
|
|
await card().getByRole('textbox').fill('http://127.0.0.1:8317/callback?state=attempt-1&code=code');
|
|
await click('Submit Callback');
|
|
await card().getByText('Completed', { exact: true }).waitFor();
|
|
assert.equal(await card().getByRole('textbox').count(), 0);
|
|
|
|
await open();
|
|
await click('Start Sign-In');
|
|
await card().getByRole('textbox').waitFor();
|
|
await page.evaluate(() => { window.devinFixture.holdStatus = true; });
|
|
await page.waitForFunction(() => window.devinFixture.releaseStatus !== null);
|
|
await click('Refresh Link');
|
|
await page.waitForFunction(() => window.devinFixture.attempts === 2);
|
|
await page.evaluate(() => window.devinFixture.releaseStatus());
|
|
await page.waitForTimeout(150);
|
|
assert.equal(await card().getByText('Completed', { exact: true }).count(), 0);
|
|
assert.ok(await card().getByRole('textbox').isVisible());
|
|
assert.ok((await calls()).some(call => call.cmd === 'management_request' && call.args.request.path === '/oauth-session' && call.args.request.query.state === 'attempt-1'));
|
|
await page.evaluate(() => { window.devinFixture.cancelError = true; });
|
|
await click('Refresh Link');
|
|
await page.getByText('Cancellation failed', { exact: false }).waitFor();
|
|
assert.equal(await page.evaluate(() => window.devinFixture.attempts), 2);
|
|
|
|
await open('view=easy');
|
|
const easy = page.locator('.simple-mode-provider-card').filter({ hasText: 'Devin OAuth' });
|
|
await easy.getByRole('button').click();
|
|
await page.waitForFunction(() => window.devinFixture.attempts === 1);
|
|
await page.evaluate(() => { window.devinFixture.status = 'ok'; });
|
|
await page.waitForFunction(() => document.querySelectorAll('.simple-mode-provider-card.connected').length === 1);
|
|
assert.ok((await calls()).some(call => call.cmd === 'start_oauth_login' && call.args.provider === 'devin'));
|
|
|
|
await open('view=files');
|
|
await page.getByText('devin-test.json', { exact: true }).waitFor();
|
|
await page.getByRole('button', { name: 'Fetch Quota', exact: true }).click();
|
|
await page.getByText('75%', { exact: false }).first().waitFor();
|
|
assert.equal(await page.locator('.devin-logo').count(), 1);
|
|
|
|
await open('view=quota');
|
|
await page.getByRole('button', { name: 'Fetch Quota', exact: true }).click();
|
|
await page.getByText('Daily quota', { exact: true }).waitFor();
|
|
assert.ok((await page.locator('.real-quota-card').innerText()).includes('Pro'));
|
|
assert.equal(await page.locator('.real-quota-track').count(), 2);
|
|
assert.equal(await page.locator('.real-quota-track span').last().evaluate(node => node.style.width), '0%');
|
|
await page.evaluate(() => { window.devinFixture.quotaError = true; });
|
|
await page.getByRole('button', { name: 'Refresh All', exact: true }).click();
|
|
await page.getByText('Session expired', { exact: false }).waitFor();
|
|
assert.equal(await page.locator('.real-quota-track').count(), 0);
|
|
|
|
fs.mkdirSync('misc', { recursive: true });
|
|
for (const theme of ['light', 'dark']) {
|
|
await open('theme=' + theme + '&locale=zh-CN');
|
|
await card().waitFor();
|
|
await page.screenshot({ path: 'misc/devin-oauth-' + theme + '.png', fullPage: true });
|
|
const filter = await page.locator('.devin-logo').evaluate(node => getComputedStyle(node).filter);
|
|
assert.equal(filter, theme === 'dark' ? 'invert(1)' : 'none');
|
|
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth));
|
|
}
|
|
assert.deepEqual(errors, []);
|
|
console.log('Devin browser checks passed: OAuth, callback validation, session refresh, simplified login, auth files, quota and both themes.');
|
|
} finally { await browser.close(); }
|
|
})().catch(error => { console.error(error); process.exitCode = 1; });
|