jupyterlab-codex-sidebar 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +9 -0
- package/.github/workflows/unit-tests.yml +27 -0
- package/.jupyterlab-playwright.log +0 -0
- package/README.md +83 -9
- package/docs/images/codex-sidebar-screenshot.png +0 -0
- package/jupyterlab_codex/handlers.py +938 -297
- package/jupyterlab_codex/labextension/package.json +13 -3
- package/jupyterlab_codex/labextension/static/525.224526d045c727069de6.js +2 -0
- package/jupyterlab_codex/labextension/static/737.e7de3ad9dd6ded798340.js +1 -0
- package/jupyterlab_codex/labextension/static/remoteEntry.6ef5e7167763a316c000.js +1 -0
- package/jupyterlab_codex/protocol.py +297 -0
- package/jupyterlab_codex/runner.py +58 -15
- package/jupyterlab_codex/sessions.py +582 -97
- package/lib/codexChat.d.ts +13 -0
- package/lib/codexChat.js +2506 -0
- package/lib/codexChat.js.map +1 -0
- package/lib/codexChatAttachmentDedup.d.ts +10 -0
- package/lib/codexChatAttachmentDedup.js +35 -0
- package/lib/codexChatAttachmentDedup.js.map +1 -0
- package/lib/codexChatAttachmentLimit.d.ts +18 -0
- package/lib/codexChatAttachmentLimit.js +50 -0
- package/lib/codexChatAttachmentLimit.js.map +1 -0
- package/lib/codexChatAttachmentState.d.ts +15 -0
- package/lib/codexChatAttachmentState.js +16 -0
- package/lib/codexChatAttachmentState.js.map +1 -0
- package/lib/codexChatDocumentUtils.d.ts +70 -0
- package/lib/codexChatDocumentUtils.js +506 -0
- package/lib/codexChatDocumentUtils.js.map +1 -0
- package/lib/codexChatFormatting.d.ts +11 -0
- package/lib/codexChatFormatting.js +83 -0
- package/lib/codexChatFormatting.js.map +1 -0
- package/lib/codexChatNotice.d.ts +3 -0
- package/lib/codexChatNotice.js +74 -0
- package/lib/codexChatNotice.js.map +1 -0
- package/lib/codexChatPersistence.d.ts +35 -0
- package/lib/codexChatPersistence.js +158 -0
- package/lib/codexChatPersistence.js.map +1 -0
- package/lib/codexChatPrimitives.d.ts +44 -0
- package/lib/codexChatPrimitives.js +156 -0
- package/lib/codexChatPrimitives.js.map +1 -0
- package/lib/codexChatRender.d.ts +24 -0
- package/lib/codexChatRender.js +293 -0
- package/lib/codexChatRender.js.map +1 -0
- package/lib/codexChatSessionFactory.d.ts +15 -0
- package/lib/codexChatSessionFactory.js +45 -0
- package/lib/codexChatSessionFactory.js.map +1 -0
- package/lib/codexChatSessionKey.d.ts +3 -0
- package/lib/codexChatSessionKey.js +14 -0
- package/lib/codexChatSessionKey.js.map +1 -0
- package/lib/codexChatStorage.d.ts +4 -0
- package/lib/codexChatStorage.js +37 -0
- package/lib/codexChatStorage.js.map +1 -0
- package/lib/codexSessionResolver.d.ts +12 -0
- package/lib/codexSessionResolver.js +38 -0
- package/lib/codexSessionResolver.js.map +1 -0
- package/lib/handlers/activitySummarizer.d.ts +15 -0
- package/lib/handlers/activitySummarizer.js +327 -0
- package/lib/handlers/activitySummarizer.js.map +1 -0
- package/lib/handlers/codexMessageTypes.d.ts +30 -0
- package/lib/handlers/codexMessageTypes.js +2 -0
- package/lib/handlers/codexMessageTypes.js.map +1 -0
- package/lib/handlers/codexMessageUtils.d.ts +46 -0
- package/lib/handlers/codexMessageUtils.js +144 -0
- package/lib/handlers/codexMessageUtils.js.map +1 -0
- package/lib/handlers/handleCodexSocketMessage.d.ts +107 -0
- package/lib/handlers/handleCodexSocketMessage.js +78 -0
- package/lib/handlers/handleCodexSocketMessage.js.map +1 -0
- package/lib/handlers/sessionSyncHandler.d.ts +34 -0
- package/lib/handlers/sessionSyncHandler.js +181 -0
- package/lib/handlers/sessionSyncHandler.js.map +1 -0
- package/lib/hooks/useCodexSocket.d.ts +15 -0
- package/lib/hooks/useCodexSocket.js +84 -0
- package/lib/hooks/useCodexSocket.js.map +1 -0
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/panel.d.ts +1 -11
- package/lib/panel.js +1 -2815
- package/lib/panel.js.map +1 -1
- package/lib/protocol.d.ts +235 -0
- package/lib/protocol.js +278 -0
- package/lib/protocol.js.map +1 -0
- package/package.json +13 -3
- package/playwright.config.cjs +27 -0
- package/playwright.unit.config.cjs +19 -0
- package/pyproject.toml +1 -1
- package/release.sh +52 -14
- package/scripts/run_playwright_e2e.sh +96 -0
- package/scripts/run_playwright_freeze_repro.sh +58 -0
- package/scripts/run_playwright_queue_repro.sh +60 -0
- package/scripts/run_playwright_repro.sh +55 -0
- package/src/codexChat.tsx +3914 -0
- package/src/codexChatAttachmentDedup.ts +47 -0
- package/src/codexChatAttachmentLimit.ts +81 -0
- package/src/codexChatAttachmentState.ts +37 -0
- package/src/codexChatDocumentUtils.ts +644 -0
- package/src/codexChatFormatting.ts +94 -0
- package/src/codexChatNotice.ts +95 -0
- package/src/codexChatPersistence.ts +191 -0
- package/src/codexChatPrimitives.tsx +446 -0
- package/src/codexChatRender.tsx +376 -0
- package/src/codexChatSessionFactory.ts +79 -0
- package/src/codexChatSessionKey.ts +16 -0
- package/src/codexChatStorage.ts +36 -0
- package/src/codexSessionResolver.ts +56 -0
- package/src/handlers/activitySummarizer.ts +369 -0
- package/src/handlers/codexMessageTypes.ts +34 -0
- package/src/handlers/codexMessageUtils.ts +217 -0
- package/src/handlers/handleCodexSocketMessage.ts +204 -0
- package/src/handlers/sessionSyncHandler.ts +308 -0
- package/src/hooks/useCodexSocket.ts +109 -0
- package/src/index.ts +1 -1
- package/src/panel.tsx +1 -4184
- package/src/protocol.ts +582 -0
- package/style/index.css +480 -11
- package/test-results/.last-run.json +4 -0
- package/test.py +0 -0
- package/tests/e2e/cell-output-error-tail.spec.js +156 -0
- package/tests/e2e/codex-ui-test-helpers.js +138 -0
- package/tests/e2e/fixtures/notebooks/error-output-tail.ipynb +58 -0
- package/tests/e2e/fixtures/notebooks/error-output-tail.py +19 -0
- package/tests/e2e/fixtures/notebooks/tab1.ipynb +322 -0
- package/tests/e2e/fixtures/notebooks/tab1.py +272 -0
- package/tests/e2e/fixtures/notebooks/tab2.ipynb +252 -0
- package/tests/e2e/fixtures/notebooks/tab2.py +231 -0
- package/tests/e2e/fixtures/notebooks/tab3.ipynb +403 -0
- package/tests/e2e/fixtures/notebooks/tab3.py +331 -0
- package/tests/e2e/fixtures/notebooks/tab4.py +339 -0
- package/tests/e2e/freeze-notebook-tabs-repro.spec.js +295 -0
- package/tests/e2e/mock-codex-cli-flood.py +127 -0
- package/tests/e2e/mock-codex-cli-prompt-echo.py +88 -0
- package/tests/e2e/mock-codex-cli.py +95 -0
- package/tests/e2e/queue-multitab-repro.spec.js +189 -0
- package/tests/test_handlers.py +116 -0
- package/tests/test_protocol.py +169 -0
- package/tests/test_session_store_limits.py +50 -0
- package/tests/unit/codexChatAttachmentDedup.spec.ts +56 -0
- package/tests/unit/codexChatAttachmentLimit.spec.ts +57 -0
- package/tests/unit/codexChatAttachmentState.spec.ts +71 -0
- package/tests/unit/codexChatDocumentUtils.spec.ts +63 -0
- package/tests/unit/codexChatLimit.spec.ts +18 -0
- package/tests/unit/codexChatNotice.spec.ts +45 -0
- package/tests/unit/codexChatPersistence.spec.ts +199 -0
- package/tests/unit/codexChatSessionFactory.spec.ts +94 -0
- package/tests/unit/codexChatSessionKey.spec.ts +18 -0
- package/tests/unit/codexMessageUtils.spec.ts +89 -0
- package/tests/unit/codexSessionResolver.spec.ts +92 -0
- package/tests/unit/handleCodexSocketMessage.spec.ts +476 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/webpack.config.js +6 -0
- package/jupyterlab_codex/labextension/static/504.335f3447c84ba3d74517.js +0 -2
- package/jupyterlab_codex/labextension/static/972.8e856719e40acc1ef4cb.js +0 -1
- package/jupyterlab_codex/labextension/static/remoteEntry.a2982f776a1f0f515640.js +0 -1
- /package/jupyterlab_codex/labextension/static/{504.335f3447c84ba3d74517.js.LICENSE.txt → 525.224526d045c727069de6.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { test, expect } = require('@playwright/test');
|
|
4
|
+
|
|
5
|
+
const NOTEBOOK_RELATIVE_PATH = 'tests/e2e/fixtures/notebooks/error-output-tail.ipynb';
|
|
6
|
+
const NOTEBOOK_NAME = 'error-output-tail.ipynb';
|
|
7
|
+
const NOTEBOOK_PY_PATH = path.resolve(__dirname, 'fixtures/notebooks/error-output-tail.py');
|
|
8
|
+
const NOTEBOOK_IPYNB_PATH = path.resolve(__dirname, 'fixtures/notebooks/error-output-tail.ipynb');
|
|
9
|
+
const TRACE_HEAD_MARKER = 'TRACE_HEAD_MARKER_ABCDEFGHIJ';
|
|
10
|
+
const TRACE_TAIL_MARKER = 'TRACE_TAIL_MARKER_ZYXWVUTSRQ';
|
|
11
|
+
|
|
12
|
+
function buildInitialNotebookUrl(baseLabUrl, notebookPath) {
|
|
13
|
+
const parsed = new URL(baseLabUrl);
|
|
14
|
+
const basePath = parsed.pathname.replace(/\/$/, '');
|
|
15
|
+
parsed.pathname = `${basePath}/tree/${notebookPath}`;
|
|
16
|
+
parsed.searchParams.set('reset', '1');
|
|
17
|
+
return parsed.toString();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function dismissBlockingDialogs(page) {
|
|
21
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
22
|
+
const dialog = page.locator('dialog.jp-Dialog, [role="dialog"].jp-Dialog, [role="dialog"]').first();
|
|
23
|
+
if (!(await dialog.count()) || !(await dialog.isVisible())) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const noKernelButton = page.getByRole('button', { name: /^No Kernel$/ }).first();
|
|
28
|
+
if ((await noKernelButton.count()) > 0 && (await noKernelButton.isVisible())) {
|
|
29
|
+
await noKernelButton.click();
|
|
30
|
+
await page.waitForTimeout(150);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await page.keyboard.press('Escape');
|
|
35
|
+
await page.waitForTimeout(150);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function ensureCodexPanel(page) {
|
|
40
|
+
await dismissBlockingDialogs(page);
|
|
41
|
+
const composer = page.locator('.jp-CodexPanel .jp-CodexComposer textarea');
|
|
42
|
+
if ((await composer.count()) > 0 && (await composer.first().isVisible())) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const codexSideTab = page
|
|
47
|
+
.locator('.jp-SideBar.jp-mod-right .lm-TabBar-tab[title="Codex"], .jp-SideBar.jp-mod-right .lm-TabBar-tab')
|
|
48
|
+
.filter({ hasText: 'Codex' })
|
|
49
|
+
.first();
|
|
50
|
+
await expect(codexSideTab).toBeVisible({ timeout: 20000 });
|
|
51
|
+
await codexSideTab.click();
|
|
52
|
+
await expect(composer.first()).toBeVisible({ timeout: 20000 });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writeNotebookFixture() {
|
|
56
|
+
const traceback = [
|
|
57
|
+
'Traceback (most recent call last):',
|
|
58
|
+
` ${TRACE_HEAD_MARKER}`,
|
|
59
|
+
`${'A'.repeat(22000)}`,
|
|
60
|
+
`ValueError: ${TRACE_TAIL_MARKER}`
|
|
61
|
+
];
|
|
62
|
+
const notebook = {
|
|
63
|
+
cells: [
|
|
64
|
+
{
|
|
65
|
+
cell_type: 'code',
|
|
66
|
+
execution_count: 1,
|
|
67
|
+
id: 'error-output-tail-cell',
|
|
68
|
+
metadata: {},
|
|
69
|
+
outputs: [
|
|
70
|
+
{
|
|
71
|
+
output_type: 'error',
|
|
72
|
+
ename: 'ValueError',
|
|
73
|
+
evalue: TRACE_TAIL_MARKER,
|
|
74
|
+
traceback
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
source: ['raise ValueError("synthetic traceback for tail truncation test")\n']
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
metadata: {
|
|
81
|
+
jupytext: {
|
|
82
|
+
formats: 'ipynb,py:percent'
|
|
83
|
+
},
|
|
84
|
+
kernelspec: {
|
|
85
|
+
display_name: 'Python 3 (ipykernel)',
|
|
86
|
+
language: 'python',
|
|
87
|
+
name: 'python3'
|
|
88
|
+
},
|
|
89
|
+
language_info: {
|
|
90
|
+
codemirror_mode: {
|
|
91
|
+
name: 'ipython',
|
|
92
|
+
version: 3
|
|
93
|
+
},
|
|
94
|
+
file_extension: '.py',
|
|
95
|
+
mimetype: 'text/x-python',
|
|
96
|
+
name: 'python',
|
|
97
|
+
nbconvert_exporter: 'python',
|
|
98
|
+
pygments_lexer: 'ipython3',
|
|
99
|
+
version: '3.13.9'
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
nbformat: 4,
|
|
103
|
+
nbformat_minor: 5
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
fs.writeFileSync(NOTEBOOK_IPYNB_PATH, `${JSON.stringify(notebook, null, 2)}\n`, 'utf8');
|
|
107
|
+
fs.writeFileSync(
|
|
108
|
+
NOTEBOOK_PY_PATH,
|
|
109
|
+
[
|
|
110
|
+
'# %%',
|
|
111
|
+
'raise ValueError("synthetic traceback for tail truncation test")',
|
|
112
|
+
''
|
|
113
|
+
].join('\n'),
|
|
114
|
+
'utf8'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
test('error cell output sends the tail of the traceback to Codex', async ({ page, baseURL }) => {
|
|
119
|
+
writeNotebookFixture();
|
|
120
|
+
const codexCommandPath =
|
|
121
|
+
process.env.PLAYWRIGHT_CODEX_COMMAND || path.resolve(__dirname, 'mock-codex-cli-prompt-echo.py');
|
|
122
|
+
const targetUrl = baseURL || process.env.JUPYTERLAB_URL || 'http://127.0.0.1:8888/lab';
|
|
123
|
+
|
|
124
|
+
await page.addInitScript(commandPath => {
|
|
125
|
+
window.localStorage.setItem('jupyterlab-codex:command-path', commandPath);
|
|
126
|
+
window.localStorage.setItem('jupyterlab-codex:include-active-cell', 'true');
|
|
127
|
+
window.localStorage.setItem('jupyterlab-codex:include-active-cell-output', 'true');
|
|
128
|
+
}, codexCommandPath);
|
|
129
|
+
|
|
130
|
+
await page.goto(buildInitialNotebookUrl(targetUrl, NOTEBOOK_RELATIVE_PATH), { waitUntil: 'domcontentloaded' });
|
|
131
|
+
await page.waitForSelector('main[aria-label="Main Content"], .jp-LabShell, .lm-DockPanel', {
|
|
132
|
+
timeout: 30000
|
|
133
|
+
});
|
|
134
|
+
await dismissBlockingDialogs(page);
|
|
135
|
+
await ensureCodexPanel(page);
|
|
136
|
+
|
|
137
|
+
const notebookCell = page.locator('.jp-Notebook .jp-Cell').first();
|
|
138
|
+
await expect(notebookCell).toBeVisible({ timeout: 20000 });
|
|
139
|
+
await notebookCell.click();
|
|
140
|
+
|
|
141
|
+
const composer = page.locator('.jp-CodexComposer textarea');
|
|
142
|
+
const sendBtn = page.locator('.jp-CodexSendBtn');
|
|
143
|
+
await composer.fill('Inspect the attached output.');
|
|
144
|
+
await expect(sendBtn).toBeEnabled({ timeout: 15000 });
|
|
145
|
+
await sendBtn.click();
|
|
146
|
+
await expect(page.locator('.jp-CodexSendBtn.is-stop')).toHaveCount(0, { timeout: 30000 });
|
|
147
|
+
|
|
148
|
+
const assistantMessage = page.locator('.jp-CodexChat-message.jp-mod-assistant').last();
|
|
149
|
+
await expect(assistantMessage).toContainText('PROMPT_TAIL_START', { timeout: 30000 });
|
|
150
|
+
const responseText = await assistantMessage.innerText();
|
|
151
|
+
|
|
152
|
+
const tailSection = responseText.split('PROMPT_TAIL_START\n')[1]?.split('\nPROMPT_TAIL_END')[0] || '';
|
|
153
|
+
expect(tailSection).toContain(TRACE_TAIL_MARKER);
|
|
154
|
+
expect(tailSection).not.toContain(TRACE_HEAD_MARKER);
|
|
155
|
+
expect(responseText).toContain('Current Cell Output:');
|
|
156
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
|
|
3
|
+
function buildNotebookUrl(baseLabUrl, notebookPath) {
|
|
4
|
+
const parsed = new URL(baseLabUrl);
|
|
5
|
+
const basePath = parsed.pathname.replace(/\/$/, '');
|
|
6
|
+
parsed.pathname = `${basePath}/tree/${notebookPath}`;
|
|
7
|
+
parsed.searchParams.set('reset', '1');
|
|
8
|
+
return parsed.toString();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function escapeRegExp(value) {
|
|
12
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function dismissBlockingDialogs(page) {
|
|
16
|
+
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
17
|
+
const dialog = page.locator('dialog.jp-Dialog, [role="dialog"].jp-Dialog, [role="dialog"]').first();
|
|
18
|
+
if (!(await dialog.count()) || !(await dialog.isVisible())) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const noKernelButton = page.getByRole('button', { name: /^No Kernel$/ }).first();
|
|
23
|
+
if ((await noKernelButton.count()) > 0 && (await noKernelButton.isVisible())) {
|
|
24
|
+
await noKernelButton.click();
|
|
25
|
+
await page.waitForTimeout(120);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const selectKernelButton = page.getByRole('button', { name: /^Select$/ }).first();
|
|
30
|
+
if ((await selectKernelButton.count()) > 0 && (await selectKernelButton.isVisible())) {
|
|
31
|
+
await selectKernelButton.click();
|
|
32
|
+
await page.waitForTimeout(120);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const cancelButton = page.getByRole('button', { name: /^Cancel$/ }).first();
|
|
37
|
+
if ((await cancelButton.count()) > 0 && (await cancelButton.isVisible())) {
|
|
38
|
+
await cancelButton.click();
|
|
39
|
+
await page.waitForTimeout(120);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await page.keyboard.press('Escape');
|
|
44
|
+
await page.waitForTimeout(120);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function ensureFileBrowserVisible(page) {
|
|
49
|
+
await dismissBlockingDialogs(page);
|
|
50
|
+
const fileBrowserTab = page.getByRole('tab', { name: /File Browser/i }).first();
|
|
51
|
+
if (await fileBrowserTab.count()) {
|
|
52
|
+
const isActiveBefore = await fileBrowserTab.evaluate(
|
|
53
|
+
element =>
|
|
54
|
+
element.getAttribute('aria-selected') === 'true' ||
|
|
55
|
+
(typeof element.className === 'string' && element.className.includes('lm-mod-current'))
|
|
56
|
+
);
|
|
57
|
+
if (!isActiveBefore) {
|
|
58
|
+
await fileBrowserTab.click();
|
|
59
|
+
}
|
|
60
|
+
const isActiveAfter = await fileBrowserTab.evaluate(
|
|
61
|
+
element =>
|
|
62
|
+
element.getAttribute('aria-selected') === 'true' ||
|
|
63
|
+
(typeof element.className === 'string' && element.className.includes('lm-mod-current'))
|
|
64
|
+
);
|
|
65
|
+
if (!isActiveAfter) {
|
|
66
|
+
await fileBrowserTab.click();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
await page.getByRole('list', { name: /files/i }).first().waitFor({ state: 'visible', timeout: 10000 }).catch(() => {});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function openDocumentFromFileBrowser(page, documentName) {
|
|
73
|
+
await ensureFileBrowserVisible(page);
|
|
74
|
+
let item = page
|
|
75
|
+
.getByRole('listitem', { name: new RegExp(`\\b${escapeRegExp(documentName)}\\b`, 'i') })
|
|
76
|
+
.first();
|
|
77
|
+
if (!(await item.count())) {
|
|
78
|
+
item = page.locator('.jp-DirListing-item', { hasText: documentName }).first();
|
|
79
|
+
}
|
|
80
|
+
if (!(await item.count())) {
|
|
81
|
+
item = page.locator('.jp-FileBrowser').getByText(documentName, { exact: true }).first();
|
|
82
|
+
}
|
|
83
|
+
await item.first().waitFor({ state: 'visible', timeout: 20000 });
|
|
84
|
+
await item.first().dblclick();
|
|
85
|
+
await dismissBlockingDialogs(page);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function activateDocumentTab(page, documentName) {
|
|
89
|
+
await dismissBlockingDialogs(page);
|
|
90
|
+
const exactTab = page
|
|
91
|
+
.getByRole('tab', { name: new RegExp(`^${escapeRegExp(documentName)}$`) })
|
|
92
|
+
.first();
|
|
93
|
+
await exactTab.click();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function ensureCodexPanel(page) {
|
|
97
|
+
await dismissBlockingDialogs(page);
|
|
98
|
+
const composer = page.locator('.jp-CodexPanel .jp-CodexComposer textarea');
|
|
99
|
+
if ((await composer.count()) > 0 && (await composer.first().isVisible())) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const codexSideTab = page
|
|
104
|
+
.locator('.jp-SideBar.jp-mod-right .lm-TabBar-tab[title="Codex"], .jp-SideBar.jp-mod-right .lm-TabBar-tab')
|
|
105
|
+
.filter({ hasText: 'Codex' })
|
|
106
|
+
.first();
|
|
107
|
+
await codexSideTab.click();
|
|
108
|
+
await expect(composer.first()).toBeVisible({ timeout: 20000 });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function openNotebookInCodex(page, baseURL, notebook) {
|
|
112
|
+
const commandPath = notebook.commandPath || path.resolve(__dirname, 'mock-codex-cli.py');
|
|
113
|
+
await page.addInitScript(cmd => {
|
|
114
|
+
window.localStorage.setItem('jupyterlab-codex:command-path', cmd);
|
|
115
|
+
if (!window.localStorage.getItem('jupyterlab-codex:model')) {
|
|
116
|
+
window.localStorage.removeItem('jupyterlab-codex:model');
|
|
117
|
+
}
|
|
118
|
+
}, commandPath);
|
|
119
|
+
|
|
120
|
+
const targetUrl = buildNotebookUrl(baseURL, notebook.path);
|
|
121
|
+
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
|
|
122
|
+
await page.waitForSelector('main[aria-label="Main Content"], .jp-LabShell, .lm-DockPanel', { timeout: 30000 });
|
|
123
|
+
await dismissBlockingDialogs(page);
|
|
124
|
+
await ensureCodexPanel(page);
|
|
125
|
+
|
|
126
|
+
await openDocumentFromFileBrowser(page, notebook.name);
|
|
127
|
+
await activateDocumentTab(page, notebook.name);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
buildNotebookUrl,
|
|
132
|
+
dismissBlockingDialogs,
|
|
133
|
+
ensureFileBrowserVisible,
|
|
134
|
+
openDocumentFromFileBrowser,
|
|
135
|
+
activateDocumentTab,
|
|
136
|
+
ensureCodexPanel,
|
|
137
|
+
openNotebookInCodex
|
|
138
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "code",
|
|
5
|
+
"execution_count": 1,
|
|
6
|
+
"id": "error-output-tail-cell",
|
|
7
|
+
"metadata": {},
|
|
8
|
+
"outputs": [
|
|
9
|
+
{
|
|
10
|
+
"ename": "ValueError",
|
|
11
|
+
"evalue": "synthetic traceback for tail truncation test",
|
|
12
|
+
"output_type": "error",
|
|
13
|
+
"traceback": [
|
|
14
|
+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
|
15
|
+
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
|
|
16
|
+
"Cell \u001b[0;32mIn[1], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msynthetic traceback for tail truncation test\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
|
|
17
|
+
"\u001b[0;31mValueError\u001b[0m: synthetic traceback for tail truncation test"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"source": [
|
|
22
|
+
"raise ValueError(\"synthetic traceback for tail truncation test\")"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"cell_type": "code",
|
|
27
|
+
"execution_count": null,
|
|
28
|
+
"id": "05fa12e1-3ea7-4081-b658-7e2799c32d07",
|
|
29
|
+
"metadata": {},
|
|
30
|
+
"outputs": [],
|
|
31
|
+
"source": []
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"metadata": {
|
|
35
|
+
"jupytext": {
|
|
36
|
+
"formats": "ipynb,py:percent"
|
|
37
|
+
},
|
|
38
|
+
"kernelspec": {
|
|
39
|
+
"display_name": "Python 3 (ipykernel)",
|
|
40
|
+
"language": "python",
|
|
41
|
+
"name": "python3"
|
|
42
|
+
},
|
|
43
|
+
"language_info": {
|
|
44
|
+
"codemirror_mode": {
|
|
45
|
+
"name": "ipython",
|
|
46
|
+
"version": 3
|
|
47
|
+
},
|
|
48
|
+
"file_extension": ".py",
|
|
49
|
+
"mimetype": "text/x-python",
|
|
50
|
+
"name": "python",
|
|
51
|
+
"nbconvert_exporter": "python",
|
|
52
|
+
"pygments_lexer": "ipython3",
|
|
53
|
+
"version": "3.12.2"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"nbformat": 4,
|
|
57
|
+
"nbformat_minor": 5
|
|
58
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# ---
|
|
2
|
+
# jupyter:
|
|
3
|
+
# jupytext:
|
|
4
|
+
# formats: ipynb,py:percent
|
|
5
|
+
# text_representation:
|
|
6
|
+
# extension: .py
|
|
7
|
+
# format_name: percent
|
|
8
|
+
# format_version: '1.3'
|
|
9
|
+
# jupytext_version: 1.17.1
|
|
10
|
+
# kernelspec:
|
|
11
|
+
# display_name: Python 3 (ipykernel)
|
|
12
|
+
# language: python
|
|
13
|
+
# name: python3
|
|
14
|
+
# ---
|
|
15
|
+
|
|
16
|
+
# %%
|
|
17
|
+
raise ValueError("synthetic traceback for tail truncation test")
|
|
18
|
+
|
|
19
|
+
# %%
|