11// Copyright (C) 2025 by Posit Software, PBC.
22
3- const WORKBENCH_BASE_URL = Cypress . env ( "WORKBENCH_URL" ) ;
4-
5- describe ( "Workbench > Positron" , { baseUrl : WORKBENCH_BASE_URL } , ( ) => {
6- // Each test must set this var to enable project-specific cleanup
7- let projectDir ;
8-
9- beforeEach ( ( ) => {
10- cy . cleanupAndRestartWorkbench ( ) ;
11-
12- cy . resetConnect ( ) ;
13- cy . setAdminCredentials ( ) ;
14- cy . visit ( "/" ) ;
15-
16- cy . loginToWorkbench ( ) ;
17- } ) ;
18-
19- afterEach ( ( ) => {
20- cy . cleanupWorkbenchData ( projectDir ) ;
21- } ) ;
22-
23- it ( "Static Content Deployment" , ( ) => {
24- projectDir = "static" ;
25-
26- cy . startWorkbenchPositronPythonProject ( projectDir ) ;
27-
28- // Publish the content
29- cy . createPositronDeployment (
30- projectDir ,
31- "index.html" ,
32- "static" ,
33- ( tomlFiles ) => {
34- const config = tomlFiles . config . contents ;
35- expect ( config . title ) . to . equal ( projectDir ) ;
36- expect ( config . type ) . to . equal ( "html" ) ;
37- expect ( config . entrypoint ) . to . equal ( "index.html" ) ;
38- expect ( config . files [ 0 ] ) . to . equal ( "/index.html" ) ;
39- expect ( config . files [ 1 ] ) . to . equal (
40- `/.posit/publish/${ tomlFiles . config . name } ` ,
41- ) ;
42- expect ( config . files [ 2 ] ) . to . equal (
43- `/.posit/publish/deployments/${ tomlFiles . contentRecord . name } ` ,
44- ) ;
45- } ,
46- ) . deployCurrentlySelected ( ) ;
47- } ) ;
48- } ) ;
3+ /**
4+ * Support file containing Workbench-specific Cypress commands
5+ */
496
507/**
51- * Logs into Workbench using default credentials and waits for the UI to load.
8+ * Logs into Workbench using default credentials and waits for the UI to load
9+ * Uses the default Workbench username/password as documented here: https://hub.docker.com/r/rstudio/rstudio-workbench
5210 * @param {string } username - The username to login with (defaults to "rstudio")
5311 * @param {string } password - The password to login with (defaults to "rstudio")
5412 */
5513Cypress . Commands . add (
56- "loginToWorkbench " ,
14+ "visitAndLoginToWorkbench " ,
5715 ( username = "rstudio" , password = "rstudio" ) => {
5816 cy . log ( `Logging into Workbench as ${ username } ` ) ;
5917
18+ cy . visit ( "/" ) ;
19+
6020 // Enter credentials and submit the form
6121 cy . get ( "#username" ) . type ( username ) ;
6222 cy . get ( "#password" ) . type ( password ) ;
@@ -102,16 +62,23 @@ Cypress.Commands.add("cleanupWorkbenchData", (projectDir) => {
10262
10363 // Remove workbench e2e projects
10464 cy . exec (
105- ' for dir in content-workspace/workbench-e2e- *; do rm -rf "$dir" 2>/dev/null || true; done' ,
65+ ` for dir in content-workspace/${ Cypress . env ( "WORKBENCH_PROJECT_PREFIX" ) } *; do rm -rf "$dir" 2>/dev/null || true; done` ,
10666 {
10767 failOnNonZeroExit : false ,
10868 } ,
10969 ) . then ( ( result ) => {
110- cy . log ( `Bash loop cleanup result: code ${ result . code } ` ) ;
111- if ( result . stderr ) cy . log ( `Cleanup stderr: ${ result . stderr } ` ) ;
70+ cy . log ( `Cleanup projects result: code ${ result . code } ` ) ;
71+ if ( result . stderr ) cy . log ( `Cleanup projects stderr: ${ result . stderr } ` ) ;
11272 } ) ;
11373} ) ;
11474
75+ /**
76+ * Cleans up and restarts the Workbench container to ensure a fresh state
77+ * This function stops the current container, removes any test data, and starts a fresh container
78+ * with clean state. It handles container lifecycle operations using the justfile commands
79+ *
80+ * @param {string } projectDir - Optional project directory to clean up specific project data
81+ */
11582Cypress . Commands . add ( "cleanupAndRestartWorkbench" , ( projectDir ) => {
11683 // Stop and remove the container
11784 cy . log ( "Stopping and removing Workbench container" ) ;
@@ -199,7 +166,7 @@ Cypress.Commands.add("startWorkbenchPositronPythonProject", () => {
199166
200167 // Set a randomized project name
201168 cy . contains ( "Set project name" ) . should ( "be.visible" ) ;
202- const projectName = `workbench-e2e- ${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 10 ) } ` ;
169+ const projectName = `${ Cypress . env ( "WORKBENCH_PROJECT_PREFIX" ) } ${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 10 ) } ` ;
203170 cy . contains ( "span" , "Enter a name for your new Python Project" )
204171 . parent ( "label" )
205172 . find ( "input" )
@@ -241,34 +208,42 @@ Cypress.Commands.add("startWorkbenchPositronPythonProject", () => {
241208 timeout : 30_000 ,
242209 } ) . should ( "be.visible" ) ;
243210
244- // TODO need some other command to wait for more stuff in the IDE before proceeding
245- // Observed a session failed to start and prevented the rest of the test from working
246-
247211 cy . log ( `Successfully created Python project: ${ projectName } ` ) ;
248212} ) ;
249213
214+ /**
215+ * Creates a new Positron deployment in Workbench
216+ *
217+ * Note: This function is derived from the VS Code deployment flow in the main Publisher extension tests,
218+ * but has been specifically adapted for the Workbench UI environment which has different navigation
219+ * patterns, element selectors, and timing considerations. In particular, the Workbench integration
220+ * requires additional steps to handle the Positron editor context and different menu structures.
221+ *
222+ * @param {string } projectDir - The directory containing the project to deploy
223+ * @param {string } entrypointFile - The primary file to use as the entrypoint (e.g., "index.html")
224+ * @param {string } title - The title to give to the deployment
225+ * @param {Function } verifyTomlCallback - Callback function to verify TOML configuration
226+ * The callback receives an object with structure: {
227+ * config: { name: string, path: string, contents: Object },
228+ * contentRecord: { name: string, path: string, contents: Object }
229+ * }
230+ * @returns {Cypress.Chainable } - Chain that can be extended with additional deployment actions
231+ */
250232Cypress . Commands . add (
251233 "createPositronDeployment" ,
252- (
253- projectDir , // string
254- entrypointFile , // string
255- title , // string
256- verifyTomlCallback , // func({config: { filename: string, contents: {},}, contentRecord: { filename: string, contents: {}})
257- ) => {
234+ ( projectDir , entrypointFile , title , verifyTomlCallback ) => {
258235 // Temporarily ignore uncaught exception due to a vscode worker being cancelled at some point
259236 cy . on ( "uncaught:exception" , ( ) => false ) ;
260237
261238 // Open the entrypoint ahead of time for easier selection later
262- // expand the subdirectory
263- if ( projectDir !== "." ) {
264- // Open the folder with our content
265- cy . get ( "button" ) . contains ( "Open Folder..." ) . click ( ) ;
266- cy . get ( ".quick-input-widget" ) . within ( ( ) => {
267- cy . get ( ".quick-input-box input" ) . should ( "be.visible" ) ;
268- cy . get ( '.monaco-list-row[aria-label="static"]' ) . click ( ) ;
269- cy . get ( ".quick-input-header a[role='button']" ) . contains ( "OK" ) . click ( ) ;
270- } ) ;
271- }
239+ // Uses the Workbench-specific file open flow
240+ // Note this deviates from the VS Code logic and does not currently handle projectDir = "."
241+ cy . get ( "button" ) . contains ( "Open Folder..." ) . click ( ) ;
242+ cy . get ( ".quick-input-widget" ) . within ( ( ) => {
243+ cy . get ( ".quick-input-box input" ) . should ( "be.visible" ) ;
244+ cy . get ( '.monaco-list-row[aria-label="static"]' ) . click ( ) ;
245+ cy . get ( ".quick-input-header a[role='button']" ) . contains ( "OK" ) . click ( ) ;
246+ } ) ;
272247
273248 // open the entrypoint file
274249 cy . get ( ".explorer-viewlet" )
@@ -281,19 +256,19 @@ Cypress.Commands.add(
281256 . find ( `[aria-label="${ entrypointFile } "]` )
282257 . should ( "be.visible" ) ;
283258
284- // activate the publisher extension
285- // Open Publisher, due to viewport size it is buried in the "..." menu
259+ // Activate the publisher extension
260+ // In Workbench the viewport size causes Publisher to be in the "..." menu
286261 cy . get (
287262 '[id="workbench.parts.activitybar"] .action-item[role="button"][aria-label="Additional Views"]' ,
288263 {
289264 timeout : 30_000 ,
290265 } ,
291266 ) . click ( ) ;
292- // Wait for popup menu to appear
267+ // Wait for popup menu that contains Publisher to appear
293268 cy . get ( '.monaco-menu .actions-container[role="menu"]' )
294269 . should ( "be.visible" )
295270 . within ( ( ) => {
296- // Finally, double-click the Posit Publisher menu item
271+ // Finally, double-click the Posit Publisher menu item, single click often fails to open it
297272 cy . findByLabelText ( "Posit Publisher" ) . dblclick ( ) ;
298273 } ) ;
299274
@@ -318,7 +293,7 @@ Cypress.Commands.add(
318293 // Create a new deployment
319294 cy . get ( ".quick-input-list" ) . contains ( "Create a New Deployment" ) . click ( ) ;
320295
321- // prompt for select entrypoint
296+ // Prompt for select entrypoint
322297 // Note this deviates from the VS Code logic and does not currently handle projectDir = "."
323298 const targetLabel = `${ entrypointFile } , Open Files` ;
324299 cy . get ( ".quick-input-widget" )
0 commit comments