@@ -36,11 +36,15 @@ class NotionServiceSession extends BrowserFollowupServiceSession {
3636
3737 await page . goto ( NOTION_INTEGRATIONS_URL ) ;
3838
39+ // Annoyingly, Notion's DOM is devoid of IDs,
40+ // so we have to use broad locators with nth.
41+
3942 // Integration name
4043 await page . getByRole ( 'textbox' ) . click ( ) ;
4144 await page . getByRole ( 'textbox' ) . fill ( generateLatchkeyAppName ( ) ) ;
42- // Workspace
45+ // Workspace - initially empty
4346 await page . getByRole ( 'button' ) . filter ( { hasText : / ^ $ / } ) . click ( ) ;
47+ // Just pick the first workspace
4448 await page . getByRole ( 'menuitem' ) . click ( ) ;
4549 // Create integration
4650 await page . getByRole ( 'button' ) . last ( ) . click ( ) ;
@@ -50,22 +54,40 @@ class NotionServiceSession extends BrowserFollowupServiceSession {
5054 . getByRole ( 'button' )
5155 . nth ( 0 )
5256 . click ( { timeout : DEFAULT_TIMEOUT_MS } ) ;
57+ // Token input
58+ const tokenTextbox = page . locator ( 'input[type="password"]' ) ;
59+ // We have to save the element handle because the same element's type changes to text after clicking "Show".
60+ const tokenTextboxElement = ( await tokenTextbox . elementHandle ( ) ) ! ;
5361 // Show
54- await page
55- . locator ( 'input[type="password"]' )
62+ await tokenTextbox
5663 . locator ( '..' )
5764 . getByRole ( 'button' )
5865 . nth ( 1 )
5966 . click ( { timeout : DEFAULT_TIMEOUT_MS } ) ;
6067
61- const tokenTextbox = page . locator ( 'input[type="password"]' ) ;
62- await tokenTextbox . waitFor ( { timeout : DEFAULT_TIMEOUT_MS } ) ;
68+ let token = '' ;
69+ // Poll for up to 2 seconds for the token to be revealed
70+ for ( let i = 0 ; i < 20 ; i ++ ) {
71+ token = ( await tokenTextboxElement . inputValue ( ) ) . trim ( ) ;
72+ if ( token !== '' ) {
73+ break ;
74+ }
75+ await page . waitForTimeout ( 100 ) ;
76+ }
6377
64- const token = await tokenTextbox . inputValue ( ) ;
6578 if ( token === '' ) {
6679 throw new LoginFailedError ( 'Failed to extract token from Notion.' ) ;
6780 }
6881
82+ // Grant access.
83+ // This part of the flow is too annoying to automate without using the labels...
84+ await page . getByRole ( 'tab' , { name : 'Access' } ) . click ( ) ;
85+ await page . getByRole ( 'button' , { name : 'Edit access' } ) . click ( ) ;
86+ await page . getByRole ( 'button' , { name : 'Private' } ) . click ( ) ;
87+ await page . getByRole ( 'button' , { name : 'Select all' } ) . click ( ) ;
88+ await page . getByRole ( 'button' , { name : 'Save' } ) . click ( ) ;
89+ await page . getByRole ( 'dialog' ) . waitFor ( { state : 'hidden' } ) ;
90+
6991 await page . close ( ) ;
7092
7193 return new AuthorizationBearer ( token ) ;
0 commit comments