2024-01-31 14:35:41 +03:00
|
|
|
/** @file Various actions, locators, and constants used in end-to-end tests. */
|
|
|
|
import * as test from '@playwright/test'
|
2024-11-15 15:12:55 +03:00
|
|
|
import * as path from 'path'
|
2024-01-31 14:35:41 +03:00
|
|
|
|
2024-09-27 10:05:10 +03:00
|
|
|
import { TEXTS } from 'enso-common/src/text'
|
2024-08-13 14:50:07 +03:00
|
|
|
|
2024-10-11 21:23:02 +03:00
|
|
|
import * as apiModule from '../api'
|
|
|
|
import DrivePageActions from './DrivePageActions'
|
|
|
|
import LoginPageActions from './LoginPageActions'
|
2024-01-31 14:35:41 +03:00
|
|
|
|
|
|
|
// =================
|
|
|
|
// === Constants ===
|
|
|
|
// =================
|
|
|
|
|
|
|
|
/** An example password that does not meet validation requirements. */
|
|
|
|
export const INVALID_PASSWORD = 'password'
|
|
|
|
/** An example password that meets validation requirements. */
|
|
|
|
export const VALID_PASSWORD = 'Password0!'
|
|
|
|
/** An example valid email address. */
|
|
|
|
export const VALID_EMAIL = 'email@example.com'
|
2024-09-27 10:05:10 +03:00
|
|
|
export const TEXT = TEXTS.english
|
2024-01-31 14:35:41 +03:00
|
|
|
|
|
|
|
// ================
|
|
|
|
// === Locators ===
|
|
|
|
// ================
|
|
|
|
|
|
|
|
// === Input locators ===
|
|
|
|
|
|
|
|
/** Find an email input (if any) on the current page. */
|
|
|
|
export function locateEmailInput(page: test.Locator | test.Page) {
|
Refactor CSS; address some design issues (#9260)
- Implement https://github.com/enso-org/cloud-v2/issues/924
- Refactor all numbers out to CSS variables
- Implement some issues raised in the design meeting
- The columns selector now only contains *hidden* columns, rather than all of them.
- Unified opacity for active (100%), selectable and hovered (75%), selectable (50%) and disabled (30%)
- Easily configurable if we want to change it in the future, so the specific values don't matter too much for now.
- Always show asset right panel if it is enabled - display placeholder text if <1 or >1 asset is selected
- Hide docs icon that was in the top right assets menubar (next to the gear icon for asset settings) (as backend functionality has yet to be implemented)
- Clicking a user in the "Shared with" column now adds them to the search as `owner:<username>`
- Add a gap between adjacent rows. This makes each row more visually distinct when many rows are selected
- Center the left column (the first column) of the context menu below the mouse, rather than centering the entire context menu.
- Fix regressions caused by CSS refactor
- Make keyboard selection indicator for asset rows rounded again
- Other misc. fixes and improvements
- Slightly modified styling of chat reaction bar
- Hide the row containing the "New Project" button in the cloud drive, when not in the "Home" drive tab
- Animate rotation of column sort arrow when clicking on a column to change the sort order
- Consistent duration of arrow rotation animation for folder arrows, column sort arrows, chat thread list arrows
- Consistent icon for sort arrow for folders and the chat thread list
- Minor adjustment of styles for optional properties in the Data Link input
Not included in this PR:
- Custom (HTML) scrollbars for consistency across all browsers and all OSes (except perhaps touchscreens)
- Potentially time-consuming to look for a library (and not quite trivial to implement ourselves)
- Columns sliding left as they expand and right as they collapse
- Also non-trivial, especially when taking into account horizontal scrolling.
- Fixing styles to closer resemble Figma design
- As (kinda) mentioned in the meeting - ideally it should be pixel perfect, *but* value consistency with other spacings, opacities etc. over being 100% pixel-perfect
- However, it has *partly* been done - mostly for the home page. It's entirely possible that changes made afterwards broke the spacing again though.
# Important Notes
None
2024-03-13 13:32:05 +03:00
|
|
|
return page.getByPlaceholder('Enter your email')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a password input (if any) on the current page. */
|
|
|
|
export function locatePasswordInput(page: test.Locator | test.Page) {
|
|
|
|
return page.getByPlaceholder('Enter your password')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "confirm password" input (if any) on the current page. */
|
|
|
|
export function locateConfirmPasswordInput(page: test.Locator | test.Page) {
|
Refactor CSS; address some design issues (#9260)
- Implement https://github.com/enso-org/cloud-v2/issues/924
- Refactor all numbers out to CSS variables
- Implement some issues raised in the design meeting
- The columns selector now only contains *hidden* columns, rather than all of them.
- Unified opacity for active (100%), selectable and hovered (75%), selectable (50%) and disabled (30%)
- Easily configurable if we want to change it in the future, so the specific values don't matter too much for now.
- Always show asset right panel if it is enabled - display placeholder text if <1 or >1 asset is selected
- Hide docs icon that was in the top right assets menubar (next to the gear icon for asset settings) (as backend functionality has yet to be implemented)
- Clicking a user in the "Shared with" column now adds them to the search as `owner:<username>`
- Add a gap between adjacent rows. This makes each row more visually distinct when many rows are selected
- Center the left column (the first column) of the context menu below the mouse, rather than centering the entire context menu.
- Fix regressions caused by CSS refactor
- Make keyboard selection indicator for asset rows rounded again
- Other misc. fixes and improvements
- Slightly modified styling of chat reaction bar
- Hide the row containing the "New Project" button in the cloud drive, when not in the "Home" drive tab
- Animate rotation of column sort arrow when clicking on a column to change the sort order
- Consistent duration of arrow rotation animation for folder arrows, column sort arrows, chat thread list arrows
- Consistent icon for sort arrow for folders and the chat thread list
- Minor adjustment of styles for optional properties in the Data Link input
Not included in this PR:
- Custom (HTML) scrollbars for consistency across all browsers and all OSes (except perhaps touchscreens)
- Potentially time-consuming to look for a library (and not quite trivial to implement ourselves)
- Columns sliding left as they expand and right as they collapse
- Also non-trivial, especially when taking into account horizontal scrolling.
- Fixing styles to closer resemble Figma design
- As (kinda) mentioned in the meeting - ideally it should be pixel perfect, *but* value consistency with other spacings, opacities etc. over being 100% pixel-perfect
- However, it has *partly* been done - mostly for the home page. It's entirely possible that changes made afterwards broke the spacing again though.
# Important Notes
None
2024-03-13 13:32:05 +03:00
|
|
|
return page.getByPlaceholder('Confirm your password')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "name" input for a "new label" modal (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateNewLabelModalNameInput(page: test.Page) {
|
2024-09-25 16:15:52 +03:00
|
|
|
return locateNewLabelModal(page).getByLabel('Name').and(page.getByRole('textbox'))
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find all color radio button inputs for a "new label" modal (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateNewLabelModalColorButtons(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
return (
|
|
|
|
locateNewLabelModal(page)
|
|
|
|
.filter({ has: page.getByText('Color') })
|
|
|
|
// The `radio` inputs are invisible, so they cannot be used in the locator.
|
Keyboard navigation between components (#9499)
- Close https://github.com/enso-org/cloud-v2/issues/982
- Add keyboard navigation via arrows between different components
- This is achieved by a `Navigator2D` class which keeps track of the closest adjacent elements.
Other changes:
- Switch much of the codebase to use `react-aria-components`
- This *should* (but does not necessarily) give us improved accessibility for free.
- Refactor various common styles into styled components
- `FocusArea` to perform automatic registration with `Navigator2D`
- `Button` and `UnstyledButton` to let buttons participate in keyboard navigation
- `HorizontalMenuBar` - used for buttons below the titles in the Drive page, Keyboard Shortcuts settings page, and Members List settings page
- `SettingsPage` in the settings pages
- `SettingsSection` in the settings page to wrap around `FocusArea` and the heading for each section
- Add debugging utilities
- Add debugging when `body` has the `data-debug` attribute: `document.body.dataset.debug = ''`
- This adds rings around elements (all with different colors):
- That are `FocusArea`s. `FocusArea` is a wrapper component that makes an element participate in `Navigator2D`.
- That are `:focus`ed, and that are `:focus-visible`
- That are `.focus-child`. This is because keyboard navigation via arrows ***ignores*** all focusable elements that are not `.focus-child`.
- Debug `Navigator2D` neighbors when `body` has the `debug-navigator2d` attribute: `document.body.dataset.debugNavigator2d = ''`
- This highlights neighbors of the currently focused element. This is a separate debug option because computing neighbors is potentially quite expensive.
# Important Notes
- :warning: Modals and the authentication flow are not yet fully tested.
- Up+Down to navigate through suggestions has been disabled to improve UX when accidentally navigating upwards to the assets search bar.
- There are a number of *known* issues with keyboard navigation. For the most part it's because a proper solution will be quite difficult.
- Focus is lost when a column (from the extra columns selector) is toggled - because the button stops existing
- It's not possible to navigate to the icons on the assets table - so it's current not possible to *hide* columns via the keyboard
- Neighbors of the extra columns selector are not ideal (both when it is being navigated from, and when it is being navigated to)
- The suggestions in the `AssetSearchBar` aren't *quite* fully integrated with arrow keyboard navigation.
- This is *semi*-intentional. I think it makes a lot more sense to integrate them in, *however* it stays like this for now largely because I think pressing `ArrowUp` then `ArrowDown` from the assets table should return to the assets table
- Likewise for the assets table. The reason here, however, is because we want multi-select. While `react-aria-components` has lists which support multi-select, it doesn't allow programmatic focus control, making it not particularly ideal, as we want to focus the topmost element when navigating in from above.
- Clicking on the "New Folder" icon (and the like) do not focus on the newly created child. This one should be pretty easy to do, but I'm not sure whether it's the right thing to do.
2024-04-05 10:21:02 +03:00
|
|
|
.locator('label[data-rac]')
|
2024-01-31 14:35:41 +03:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "name" input for an "upsert secret" modal (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateSecretNameInput(page: test.Page) {
|
2024-08-29 08:54:21 +03:00
|
|
|
return locateUpsertSecretModal(page).getByPlaceholder(TEXT.secretNamePlaceholder)
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "value" input for an "upsert secret" modal (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateSecretValueInput(page: test.Page) {
|
2024-08-29 08:54:21 +03:00
|
|
|
return locateUpsertSecretModal(page).getByPlaceholder(TEXT.secretValuePlaceholder)
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a search bar input (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateSearchBarInput(page: test.Page) {
|
2024-06-19 18:24:11 +03:00
|
|
|
return locateSearchBar(page).getByPlaceholder(/(?:)/)
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find the name column of the given assets table row. */
|
|
|
|
export function locateAssetRowName(locator: test.Locator) {
|
|
|
|
return locator.getByTestId('asset-row-name')
|
|
|
|
}
|
|
|
|
|
|
|
|
// === Button locators ===
|
|
|
|
|
2024-02-26 18:50:00 +03:00
|
|
|
/** Find a "login" button (if any) on the current locator. */
|
2024-01-31 14:35:41 +03:00
|
|
|
export function locateLoginButton(page: test.Locator | test.Page) {
|
|
|
|
return page.getByRole('button', { name: 'Login', exact: true }).getByText('Login')
|
|
|
|
}
|
|
|
|
|
2024-02-26 18:50:00 +03:00
|
|
|
/** Find a "register" button (if any) on the current locator. */
|
2024-01-31 14:35:41 +03:00
|
|
|
export function locateRegisterButton(page: test.Locator | test.Page) {
|
|
|
|
return page.getByRole('button', { name: 'Register' }).getByText('Register')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "create" button (if any) on the current page. */
|
|
|
|
export function locateCreateButton(page: test.Locator | test.Page) {
|
|
|
|
return page.getByRole('button', { name: 'Create' }).getByText('Create')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a button to open the editor (if any) on the current page. */
|
|
|
|
export function locatePlayOrOpenProjectButton(page: test.Locator | test.Page) {
|
2024-06-13 19:57:50 +03:00
|
|
|
return page.getByLabel('Open in editor')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a button to close the project (if any) on the current page. */
|
|
|
|
export function locateStopProjectButton(page: test.Locator | test.Page) {
|
2024-06-13 19:57:50 +03:00
|
|
|
return page.getByLabel('Stop execution')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
2024-06-24 19:02:22 +03:00
|
|
|
/** Close a modal. */
|
|
|
|
export function closeModal(page: test.Page) {
|
|
|
|
return test.test.step('Close modal', async () => {
|
|
|
|
await page.getByLabel('Close').click()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-31 14:35:41 +03:00
|
|
|
/** Find all labels in the labels panel (if any) on the current page. */
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
export function locateLabelsPanelLabels(page: test.Page, name?: string) {
|
2024-01-31 14:35:41 +03:00
|
|
|
return (
|
|
|
|
locateLabelsPanel(page)
|
|
|
|
.getByRole('button')
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
.filter(name != null ? { has: page.getByText(name) } : {})
|
2024-01-31 14:35:41 +03:00
|
|
|
// The delete button is also a `button`.
|
|
|
|
.and(page.locator(':nth-child(1)'))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a tick button (if any) on the current page. */
|
|
|
|
export function locateEditingTick(page: test.Locator | test.Page) {
|
Fix various issues on the Dashboard (#10256)
- Fix React DevTools not working in Firefox
- Fix selection of asset names when editing them, not working at all in Firefox
- Convert tick/cross buttons when editing assets, and the "plus" and "reload" buttons on the "shared with" column, "labels" column, and keyboard shortcuts table, to match more with the rest of the design
- Update clip path when the container resizes, so that the icons for hidden columns never overlap the actual table header
- Fix #10184
- Fix renames being committed even when cancelling
- Fix duplicate name detection - previously, all asset types only checked folders with the same name, not assets with the same name
- I'm not 100% sure this is the correct behavior still
- Stop using `kbd` (`aria.Keyboard`) to display keyboard shortcuts, since they should not be displayed in a monospace font.
- Fix "plus" and "reload" buttons going past the right side of their parent table cell
- Limit length of `PermissionDisplay` - if the username of a user with permission is too long, it uses a tooltip instead
- Update the username dynamically for all permissions owned by self, when changing username in the settings.
- This avoids having to fully invalidate the directory tree every time the username changes, given that nothing changes about the assets' metadata themselves.
- Cache children in the Drive tree
- This avoids loading spinners when closing a folder and immediately reopening it.
- Note that children are still re-fetched on reopen to ensure freshness
# Important Notes
- This MAY be split into multiple smaller PRs. However, I think it's better to QA as a single PR, to avoid duplicating work checking behavior that may be changed by a sibling PR (assuming the PR was split into multiple).
2024-06-20 21:30:24 +03:00
|
|
|
return page.getByLabel('Confirm Edit')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a cross button (if any) on the current page. */
|
|
|
|
export function locateEditingCross(page: test.Locator | test.Page) {
|
Fix various issues on the Dashboard (#10256)
- Fix React DevTools not working in Firefox
- Fix selection of asset names when editing them, not working at all in Firefox
- Convert tick/cross buttons when editing assets, and the "plus" and "reload" buttons on the "shared with" column, "labels" column, and keyboard shortcuts table, to match more with the rest of the design
- Update clip path when the container resizes, so that the icons for hidden columns never overlap the actual table header
- Fix #10184
- Fix renames being committed even when cancelling
- Fix duplicate name detection - previously, all asset types only checked folders with the same name, not assets with the same name
- I'm not 100% sure this is the correct behavior still
- Stop using `kbd` (`aria.Keyboard`) to display keyboard shortcuts, since they should not be displayed in a monospace font.
- Fix "plus" and "reload" buttons going past the right side of their parent table cell
- Limit length of `PermissionDisplay` - if the username of a user with permission is too long, it uses a tooltip instead
- Update the username dynamically for all permissions owned by self, when changing username in the settings.
- This avoids having to fully invalidate the directory tree every time the username changes, given that nothing changes about the assets' metadata themselves.
- Cache children in the Drive tree
- This avoids loading spinners when closing a folder and immediately reopening it.
- Note that children are still re-fetched on reopen to ensure freshness
# Important Notes
- This MAY be split into multiple smaller PRs. However, I think it's better to QA as a single PR, to avoid duplicating work checking behavior that may be changed by a sibling PR (assuming the PR was split into multiple).
2024-06-20 21:30:24 +03:00
|
|
|
return page.getByLabel('Cancel Edit')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find labels in the "Labels" column of the assets table (if any) on the current page. */
|
|
|
|
export function locateAssetLabels(page: test.Locator | test.Page) {
|
|
|
|
return page.getByTestId('asset-label')
|
|
|
|
}
|
|
|
|
|
2024-03-18 11:47:01 +03:00
|
|
|
/** Find a toggle for the "Name" column (if any) on the current page. */
|
|
|
|
export function locateNameColumnToggle(page: test.Locator | test.Page) {
|
2024-09-27 10:05:10 +03:00
|
|
|
return page.getByLabel('Name')
|
2024-03-18 11:47:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a toggle for the "Modified" column (if any) on the current page. */
|
|
|
|
export function locateModifiedColumnToggle(page: test.Locator | test.Page) {
|
2024-09-27 10:05:10 +03:00
|
|
|
return page.getByLabel('Modified')
|
2024-03-18 11:47:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a toggle for the "Shared with" column (if any) on the current page. */
|
|
|
|
export function locateSharedWithColumnToggle(page: test.Locator | test.Page) {
|
2024-09-27 10:05:10 +03:00
|
|
|
return page.getByLabel('Shared With')
|
2024-03-18 11:47:01 +03:00
|
|
|
}
|
|
|
|
|
2024-01-31 14:35:41 +03:00
|
|
|
/** Find a toggle for the "Labels" column (if any) on the current page. */
|
|
|
|
export function locateLabelsColumnToggle(page: test.Locator | test.Page) {
|
2024-09-27 10:05:10 +03:00
|
|
|
return page.getByLabel('Labels')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a toggle for the "Accessed by projects" column (if any) on the current page. */
|
|
|
|
export function locateAccessedByProjectsColumnToggle(page: test.Locator | test.Page) {
|
2024-09-27 10:05:10 +03:00
|
|
|
return page.getByLabel('Accessed By Projects')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a toggle for the "Accessed data" column (if any) on the current page. */
|
|
|
|
export function locateAccessedDataColumnToggle(page: test.Locator | test.Page) {
|
2024-09-27 10:05:10 +03:00
|
|
|
return page.getByLabel('Accessed Data')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a toggle for the "Docs" column (if any) on the current page. */
|
|
|
|
export function locateDocsColumnToggle(page: test.Locator | test.Page) {
|
2024-09-27 10:05:10 +03:00
|
|
|
return page.getByLabel('Docs')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a button for the "Recent" category (if any) on the current page. */
|
|
|
|
export function locateRecentCategory(page: test.Locator | test.Page) {
|
2024-05-10 11:17:06 +03:00
|
|
|
return page.getByLabel('Recent').locator('visible=true')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a button for the "Home" category (if any) on the current page. */
|
|
|
|
export function locateHomeCategory(page: test.Locator | test.Page) {
|
2024-05-10 11:17:06 +03:00
|
|
|
return page.getByLabel('Home').locator('visible=true')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a button for the "Trash" category (if any) on the current page. */
|
|
|
|
export function locateTrashCategory(page: test.Locator | test.Page) {
|
2024-05-10 11:17:06 +03:00
|
|
|
return page.getByLabel('Trash').locator('visible=true')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
2024-06-20 19:19:01 +03:00
|
|
|
// === Other buttons ===
|
2024-01-31 14:35:41 +03:00
|
|
|
|
|
|
|
/** Find a "new label" button (if any) on the current page. */
|
|
|
|
export function locateNewLabelButton(page: test.Locator | test.Page) {
|
|
|
|
return page.getByRole('button', { name: 'new label' }).getByText('new label')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find an "upgrade" button (if any) on the current page. */
|
|
|
|
export function locateUpgradeButton(page: test.Locator | test.Page) {
|
2024-06-13 19:57:50 +03:00
|
|
|
return page.getByRole('link', { name: 'Upgrade', exact: true }).getByText('Upgrade').first()
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
2024-06-20 19:19:01 +03:00
|
|
|
/** Find a not enabled stub view (if any) on the current page. */
|
2024-05-10 15:28:49 +03:00
|
|
|
export function locateNotEnabledStub(page: test.Locator | test.Page) {
|
|
|
|
return page.getByTestId('not-enabled-stub')
|
|
|
|
}
|
|
|
|
|
2024-01-31 14:35:41 +03:00
|
|
|
/** Find a "new folder" icon (if any) on the current page. */
|
|
|
|
export function locateNewFolderIcon(page: test.Locator | test.Page) {
|
2024-08-01 14:29:05 +03:00
|
|
|
return page.getByRole('button', { name: 'New Folder', exact: true })
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "new secret" icon (if any) on the current page. */
|
|
|
|
export function locateNewSecretIcon(page: test.Locator | test.Page) {
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
return page.getByRole('button', { name: 'New Secret' })
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "download files" icon (if any) on the current page. */
|
|
|
|
export function locateDownloadFilesIcon(page: test.Locator | test.Page) {
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
return page.getByRole('button', { name: 'Export' })
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a list of tags in the search bar (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateSearchBarTags(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
return locateSearchBar(page).getByTestId('asset-search-tag-names').getByRole('button')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a list of labels in the search bar (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateSearchBarLabels(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
return locateSearchBar(page).getByTestId('asset-search-labels').getByRole('button')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a list of labels in the search bar (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateSearchBarSuggestions(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
return locateSearchBar(page).getByTestId('asset-search-suggestion')
|
|
|
|
}
|
|
|
|
|
|
|
|
// === Icon locators ===
|
|
|
|
|
|
|
|
// These are specifically icons that are not also buttons.
|
|
|
|
// Icons that *are* buttons belong in the "Button locators" section.
|
|
|
|
|
|
|
|
/** Find a "sort ascending" icon (if any) on the current page. */
|
|
|
|
export function locateSortAscendingIcon(page: test.Locator | test.Page) {
|
|
|
|
return page.getByAltText('Sort Ascending')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "sort descending" icon (if any) on the current page. */
|
|
|
|
export function locateSortDescendingIcon(page: test.Locator | test.Page) {
|
|
|
|
return page.getByAltText('Sort Descending')
|
|
|
|
}
|
|
|
|
|
2024-06-20 19:19:01 +03:00
|
|
|
// === Heading locators ===
|
2024-01-31 14:35:41 +03:00
|
|
|
|
|
|
|
/** Find a "name" column heading (if any) on the current page. */
|
|
|
|
export function locateNameColumnHeading(page: test.Locator | test.Page) {
|
2024-06-20 19:19:01 +03:00
|
|
|
return page
|
|
|
|
.getByLabel('Sort by name')
|
|
|
|
.or(page.getByLabel('Stop sorting by name'))
|
|
|
|
.or(page.getByLabel('Sort by name descending'))
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "modified" column heading (if any) on the current page. */
|
|
|
|
export function locateModifiedColumnHeading(page: test.Locator | test.Page) {
|
|
|
|
return page
|
Keyboard navigation between components (#9499)
- Close https://github.com/enso-org/cloud-v2/issues/982
- Add keyboard navigation via arrows between different components
- This is achieved by a `Navigator2D` class which keeps track of the closest adjacent elements.
Other changes:
- Switch much of the codebase to use `react-aria-components`
- This *should* (but does not necessarily) give us improved accessibility for free.
- Refactor various common styles into styled components
- `FocusArea` to perform automatic registration with `Navigator2D`
- `Button` and `UnstyledButton` to let buttons participate in keyboard navigation
- `HorizontalMenuBar` - used for buttons below the titles in the Drive page, Keyboard Shortcuts settings page, and Members List settings page
- `SettingsPage` in the settings pages
- `SettingsSection` in the settings page to wrap around `FocusArea` and the heading for each section
- Add debugging utilities
- Add debugging when `body` has the `data-debug` attribute: `document.body.dataset.debug = ''`
- This adds rings around elements (all with different colors):
- That are `FocusArea`s. `FocusArea` is a wrapper component that makes an element participate in `Navigator2D`.
- That are `:focus`ed, and that are `:focus-visible`
- That are `.focus-child`. This is because keyboard navigation via arrows ***ignores*** all focusable elements that are not `.focus-child`.
- Debug `Navigator2D` neighbors when `body` has the `debug-navigator2d` attribute: `document.body.dataset.debugNavigator2d = ''`
- This highlights neighbors of the currently focused element. This is a separate debug option because computing neighbors is potentially quite expensive.
# Important Notes
- :warning: Modals and the authentication flow are not yet fully tested.
- Up+Down to navigate through suggestions has been disabled to improve UX when accidentally navigating upwards to the assets search bar.
- There are a number of *known* issues with keyboard navigation. For the most part it's because a proper solution will be quite difficult.
- Focus is lost when a column (from the extra columns selector) is toggled - because the button stops existing
- It's not possible to navigate to the icons on the assets table - so it's current not possible to *hide* columns via the keyboard
- Neighbors of the extra columns selector are not ideal (both when it is being navigated from, and when it is being navigated to)
- The suggestions in the `AssetSearchBar` aren't *quite* fully integrated with arrow keyboard navigation.
- This is *semi*-intentional. I think it makes a lot more sense to integrate them in, *however* it stays like this for now largely because I think pressing `ArrowUp` then `ArrowDown` from the assets table should return to the assets table
- Likewise for the assets table. The reason here, however, is because we want multi-select. While `react-aria-components` has lists which support multi-select, it doesn't allow programmatic focus control, making it not particularly ideal, as we want to focus the topmost element when navigating in from above.
- Clicking on the "New Folder" icon (and the like) do not focus on the newly created child. This one should be pretty easy to do, but I'm not sure whether it's the right thing to do.
2024-04-05 10:21:02 +03:00
|
|
|
.getByLabel('Sort by modification date')
|
|
|
|
.or(page.getByLabel('Stop sorting by modification date'))
|
2024-06-20 19:19:01 +03:00
|
|
|
.or(page.getByLabel('Sort by modification date descending'))
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// === Container locators ===
|
|
|
|
|
|
|
|
/** Find a drive view (if any) on the current page. */
|
|
|
|
export function locateDriveView(page: test.Locator | test.Page) {
|
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('drive-view')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a samples list (if any) on the current page. */
|
|
|
|
export function locateSamplesList(page: test.Locator | test.Page) {
|
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('samples')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find all samples list (if any) on the current page. */
|
|
|
|
export function locateSamples(page: test.Locator | test.Page) {
|
|
|
|
// This has no identifying features.
|
|
|
|
return locateSamplesList(page).getByRole('button')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find an editor container (if any) on the current page. */
|
|
|
|
export function locateEditor(page: test.Page) {
|
2024-05-27 20:32:42 +03:00
|
|
|
// Test ID of a placeholder editor component used during testing.
|
2024-10-11 21:23:02 +03:00
|
|
|
return page.locator('.App')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find an assets table (if any) on the current page. */
|
|
|
|
export function locateAssetsTable(page: test.Page) {
|
|
|
|
return locateDriveView(page).getByRole('table')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find assets table rows (if any) on the current page. */
|
|
|
|
export function locateAssetRows(page: test.Page) {
|
2024-08-08 15:12:05 +03:00
|
|
|
return locateAssetsTable(page).getByTestId('asset-row')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
2024-08-12 13:25:18 +03:00
|
|
|
/** Find assets table placeholder rows (if any) on the current page. */
|
|
|
|
export function locateNonAssetRows(page: test.Page) {
|
|
|
|
return locateAssetsTable(page).locator('tbody tr:not([data-testid="asset-row"])')
|
|
|
|
}
|
|
|
|
|
2024-01-31 14:35:41 +03:00
|
|
|
/** Find the name column of the given asset row. */
|
|
|
|
export function locateAssetName(locator: test.Locator) {
|
|
|
|
return locator.locator('> :nth-child(1)')
|
|
|
|
}
|
|
|
|
|
2024-10-11 21:23:02 +03:00
|
|
|
/**
|
|
|
|
* Find assets table rows that represent directories that can be expanded (if any)
|
|
|
|
* on the current page.
|
|
|
|
*/
|
2024-01-31 14:35:41 +03:00
|
|
|
export function locateExpandableDirectories(page: test.Page) {
|
2024-06-19 18:24:11 +03:00
|
|
|
// The icon is hidden when not hovered so `getByLabel` will not work.
|
|
|
|
return locateAssetRows(page).filter({ has: page.locator('[aria-label=Expand]') })
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
2024-10-11 21:23:02 +03:00
|
|
|
/**
|
|
|
|
* Find assets table rows that represent directories that can be collapsed (if any)
|
|
|
|
* on the current page.
|
|
|
|
*/
|
2024-01-31 14:35:41 +03:00
|
|
|
export function locateCollapsibleDirectories(page: test.Page) {
|
2024-06-19 18:24:11 +03:00
|
|
|
// The icon is hidden when not hovered so `getByLabel` will not work.
|
|
|
|
return locateAssetRows(page).filter({ has: page.locator('[aria-label=Collapse]') })
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "new label" modal (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateNewLabelModal(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('new-label-modal')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find an "upsert secret" modal (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateUpsertSecretModal(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('upsert-secret-modal')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a user menu (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateUserMenu(page: test.Page) {
|
2024-09-27 10:05:10 +03:00
|
|
|
return page.getByLabel(TEXT.userMenuLabel).and(page.getByRole('button')).locator('visible=true')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "set username" panel (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateSetUsernamePanel(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('set-username-panel')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a set of context menus (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateContextMenus(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('context-menus')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a labels panel (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateLabelsPanel(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('labels')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a list of labels (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateLabelsList(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('labels-list')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find an asset panel (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateAssetPanel(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
// This has no identifying features.
|
2024-06-20 19:19:01 +03:00
|
|
|
return page.getByTestId('asset-panel').locator('visible=true')
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a search bar (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateSearchBar(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('asset-search-bar')
|
|
|
|
}
|
|
|
|
|
2024-03-18 11:47:01 +03:00
|
|
|
/** Find an extra columns button panel (if any) on the current page. */
|
|
|
|
export function locateExtraColumns(page: test.Page) {
|
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('extra-columns')
|
|
|
|
}
|
|
|
|
|
2024-10-11 21:23:02 +03:00
|
|
|
/**
|
|
|
|
* Find a root directory dropzone (if any) on the current page.
|
2024-03-18 11:47:01 +03:00
|
|
|
* This is the empty space below the assets table, if it doesn't take up the whole screen
|
2024-10-11 21:23:02 +03:00
|
|
|
* vertically.
|
|
|
|
*/
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateRootDirectoryDropzone(page: test.Page) {
|
|
|
|
// This has no identifying features.
|
|
|
|
return page.getByTestId('root-directory-dropzone')
|
|
|
|
}
|
|
|
|
|
2024-01-31 14:35:41 +03:00
|
|
|
// === Content locators ===
|
|
|
|
|
|
|
|
/** Find an asset description in an asset panel (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateAssetPanelDescription(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
// This has no identifying features.
|
|
|
|
return locateAssetPanel(page).getByTestId('asset-panel-description')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find asset permissions in an asset panel (if any) on the current page. */
|
2024-03-18 11:47:01 +03:00
|
|
|
export function locateAssetPanelPermissions(page: test.Page) {
|
2024-01-31 14:35:41 +03:00
|
|
|
// This has no identifying features.
|
|
|
|
return locateAssetPanel(page).getByTestId('asset-panel-permissions').getByRole('button')
|
|
|
|
}
|
|
|
|
|
2024-06-20 19:19:01 +03:00
|
|
|
export namespace settings {
|
|
|
|
export namespace tab {
|
|
|
|
export namespace organization {
|
|
|
|
/** Find an "organization" tab button. */
|
|
|
|
export function locate(page: test.Page) {
|
|
|
|
return page.getByRole('button', { name: 'Organization' }).getByText('Organization')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export namespace members {
|
|
|
|
/** Find a "members" tab button. */
|
|
|
|
export function locate(page: test.Page) {
|
|
|
|
return page.getByRole('button', { name: 'Members', exact: true }).getByText('Members')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export namespace userAccount {
|
|
|
|
/** Navigate so that the "user account" settings section is visible. */
|
|
|
|
export async function go(page: test.Page) {
|
|
|
|
await test.test.step('Go to "user account" settings section', async () => {
|
2024-07-16 12:55:45 +03:00
|
|
|
await locateUserMenu(page).click()
|
|
|
|
await page.getByRole('button', { name: 'Settings' }).getByText('Settings').click()
|
2024-06-20 19:19:01 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "user account" settings section. */
|
|
|
|
export function locate(page: test.Page) {
|
|
|
|
return page.getByRole('heading').and(page.getByText('User Account')).locator('..')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "name" input in the "user account" settings section. */
|
|
|
|
export function locateNameInput(page: test.Page) {
|
|
|
|
return locate(page).getByLabel('Name')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export namespace changePassword {
|
|
|
|
/** Navigate so that the "change password" settings section is visible. */
|
|
|
|
export async function go(page: test.Page) {
|
|
|
|
await test.test.step('Go to "change password" settings section', async () => {
|
2024-07-16 12:55:45 +03:00
|
|
|
await locateUserMenu(page).click()
|
|
|
|
await page.getByRole('button', { name: 'Settings' }).getByText('Settings').click()
|
2024-06-20 19:19:01 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "change password" settings section. */
|
|
|
|
export function locate(page: test.Page) {
|
|
|
|
return page.getByRole('heading').and(page.getByText('Change Password')).locator('..')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "current password" input in the "user account" settings section. */
|
|
|
|
export function locateCurrentPasswordInput(page: test.Page) {
|
2024-08-13 14:50:07 +03:00
|
|
|
return locate(page).getByRole('group', { name: 'Current password' }).getByRole('textbox')
|
2024-06-20 19:19:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "new password" input in the "user account" settings section. */
|
|
|
|
export function locateNewPasswordInput(page: test.Page) {
|
2024-08-13 14:50:07 +03:00
|
|
|
return locate(page)
|
|
|
|
.getByRole('group', { name: /^New password/, exact: true })
|
|
|
|
.getByRole('textbox')
|
2024-06-20 19:19:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "confirm new password" input in the "user account" settings section. */
|
|
|
|
export function locateConfirmNewPasswordInput(page: test.Page) {
|
2024-08-13 14:50:07 +03:00
|
|
|
return locate(page)
|
|
|
|
.getByRole('group', { name: /^Confirm new password/, exact: true })
|
|
|
|
.getByRole('textbox')
|
2024-06-20 19:19:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "change" button. */
|
|
|
|
export function locateChangeButton(page: test.Page) {
|
|
|
|
return locate(page).getByRole('button', { name: 'Change' }).getByText('Change')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export namespace profilePicture {
|
|
|
|
/** Navigate so that the "profile picture" settings section is visible. */
|
|
|
|
export async function go(page: test.Page) {
|
|
|
|
await test.test.step('Go to "profile picture" settings section', async () => {
|
2024-07-16 12:55:45 +03:00
|
|
|
await locateUserMenu(page).click()
|
|
|
|
await page.getByRole('button', { name: 'Settings' }).getByText('Settings').click()
|
2024-06-20 19:19:01 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "profile picture" settings section. */
|
|
|
|
export function locate(page: test.Page) {
|
|
|
|
return page.getByRole('heading').and(page.getByText('Profile Picture')).locator('..')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "profile picture" input. */
|
|
|
|
export function locateInput(page: test.Page) {
|
|
|
|
return locate(page).locator('label')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export namespace organization {
|
|
|
|
/** Navigate so that the "organization" settings section is visible. */
|
|
|
|
export async function go(page: test.Page) {
|
|
|
|
await test.test.step('Go to "organization" settings section', async () => {
|
2024-07-16 12:55:45 +03:00
|
|
|
await locateUserMenu(page).click()
|
|
|
|
await page.getByRole('button', { name: 'Settings' }).getByText('Settings').click()
|
2024-06-20 19:19:01 +03:00
|
|
|
await settings.tab.organization.locate(page).click()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find an "organization" settings section. */
|
|
|
|
export function locate(page: test.Page) {
|
|
|
|
return page.getByRole('heading').and(page.getByText('Organization')).locator('..')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "name" input in the "organization" settings section. */
|
|
|
|
export function locateNameInput(page: test.Page) {
|
|
|
|
return locate(page).getByLabel('Organization display name')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find an "email" input in the "organization" settings section. */
|
|
|
|
export function locateEmailInput(page: test.Page) {
|
|
|
|
return locate(page).getByLabel('Email')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find an "website" input in the "organization" settings section. */
|
|
|
|
export function locateWebsiteInput(page: test.Page) {
|
|
|
|
return locate(page).getByLabel('Website')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find an "location" input in the "organization" settings section. */
|
|
|
|
export function locateLocationInput(page: test.Page) {
|
|
|
|
return locate(page).getByLabel('Location')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export namespace organizationProfilePicture {
|
|
|
|
/** Navigate so that the "organization profile picture" settings section is visible. */
|
|
|
|
export async function go(page: test.Page) {
|
|
|
|
await test.test.step('Go to "organization profile picture" settings section', async () => {
|
2024-07-16 12:55:45 +03:00
|
|
|
await locateUserMenu(page).click()
|
|
|
|
await page.getByRole('button', { name: 'Settings' }).getByText('Settings').click()
|
2024-06-20 19:19:01 +03:00
|
|
|
await settings.tab.organization.locate(page).click()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find an "organization profile picture" settings section. */
|
|
|
|
export function locate(page: test.Page) {
|
|
|
|
return page.getByRole('heading').and(page.getByText('Profile Picture')).locator('..')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "profile picture" input. */
|
|
|
|
export function locateInput(page: test.Page) {
|
|
|
|
return locate(page).locator('label')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export namespace members {
|
|
|
|
/** Navigate so that the "members" settings section is visible. */
|
|
|
|
export async function go(page: test.Page, force = false) {
|
|
|
|
await test.test.step('Go to "members" settings section', async () => {
|
2024-07-16 12:55:45 +03:00
|
|
|
await locateUserMenu(page).click()
|
|
|
|
await page.getByRole('button', { name: 'Settings' }).getByText('Settings').click()
|
2024-06-20 19:19:01 +03:00
|
|
|
await settings.tab.members.locate(page).click({ force })
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find a "members" settings section. */
|
|
|
|
export function locate(page: test.Page) {
|
|
|
|
return page.getByRole('heading').and(page.getByText('Members')).locator('..')
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Find all rows representing members of the current organization. */
|
|
|
|
export function locateMembersRows(page: test.Page) {
|
|
|
|
return locate(page).locator('tbody').getByRole('row')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-31 14:35:41 +03:00
|
|
|
// ===============================
|
|
|
|
// === Visual layout utilities ===
|
|
|
|
// ===============================
|
|
|
|
|
2024-10-11 21:23:02 +03:00
|
|
|
/**
|
|
|
|
* Get the left side of the bounding box of an asset row. The locator MUST be for an asset row.
|
2024-01-31 14:35:41 +03:00
|
|
|
* DO NOT assume the left side of the outer container will change. This means that it is NOT SAFE
|
2024-10-11 21:23:02 +03:00
|
|
|
* to do anything with the returned values other than comparing them.
|
|
|
|
*/
|
2024-01-31 14:35:41 +03:00
|
|
|
export function getAssetRowLeftPx(locator: test.Locator) {
|
|
|
|
return locator.evaluate((el) => el.children[0]?.children[0]?.getBoundingClientRect().left ?? 0)
|
|
|
|
}
|
|
|
|
|
2024-02-19 14:47:48 +03:00
|
|
|
// ===================================
|
2024-06-20 19:19:01 +03:00
|
|
|
// === Expect functions for themes ===
|
2024-02-19 14:47:48 +03:00
|
|
|
// ===================================
|
|
|
|
|
|
|
|
/** A test assertion to confirm that the element has the class `selected`. */
|
|
|
|
export async function expectClassSelected(locator: test.Locator) {
|
|
|
|
await test.test.step('Expect `selected`', async () => {
|
|
|
|
await test.expect(locator).toHaveClass(/(?:^| )selected(?: |$)/)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-20 19:19:01 +03:00
|
|
|
// ==============================
|
|
|
|
// === Other expect functions ===
|
|
|
|
// ==============================
|
Refactor CSS; address some design issues (#9260)
- Implement https://github.com/enso-org/cloud-v2/issues/924
- Refactor all numbers out to CSS variables
- Implement some issues raised in the design meeting
- The columns selector now only contains *hidden* columns, rather than all of them.
- Unified opacity for active (100%), selectable and hovered (75%), selectable (50%) and disabled (30%)
- Easily configurable if we want to change it in the future, so the specific values don't matter too much for now.
- Always show asset right panel if it is enabled - display placeholder text if <1 or >1 asset is selected
- Hide docs icon that was in the top right assets menubar (next to the gear icon for asset settings) (as backend functionality has yet to be implemented)
- Clicking a user in the "Shared with" column now adds them to the search as `owner:<username>`
- Add a gap between adjacent rows. This makes each row more visually distinct when many rows are selected
- Center the left column (the first column) of the context menu below the mouse, rather than centering the entire context menu.
- Fix regressions caused by CSS refactor
- Make keyboard selection indicator for asset rows rounded again
- Other misc. fixes and improvements
- Slightly modified styling of chat reaction bar
- Hide the row containing the "New Project" button in the cloud drive, when not in the "Home" drive tab
- Animate rotation of column sort arrow when clicking on a column to change the sort order
- Consistent duration of arrow rotation animation for folder arrows, column sort arrows, chat thread list arrows
- Consistent icon for sort arrow for folders and the chat thread list
- Minor adjustment of styles for optional properties in the Data Link input
Not included in this PR:
- Custom (HTML) scrollbars for consistency across all browsers and all OSes (except perhaps touchscreens)
- Potentially time-consuming to look for a library (and not quite trivial to implement ourselves)
- Columns sliding left as they expand and right as they collapse
- Also non-trivial, especially when taking into account horizontal scrolling.
- Fixing styles to closer resemble Figma design
- As (kinda) mentioned in the meeting - ideally it should be pixel perfect, *but* value consistency with other spacings, opacities etc. over being 100% pixel-perfect
- However, it has *partly* been done - mostly for the home page. It's entirely possible that changes made afterwards broke the spacing again though.
# Important Notes
None
2024-03-13 13:32:05 +03:00
|
|
|
|
2024-06-20 19:19:01 +03:00
|
|
|
/** A test assertion to confirm that the element is fully transparent. */
|
|
|
|
export async function expectOpacity0(locator: test.Locator) {
|
|
|
|
await test.test.step('Expect `opacity: 0`', async () => {
|
|
|
|
await test
|
|
|
|
.expect(async () => {
|
|
|
|
test.expect(await locator.evaluate((el) => getComputedStyle(el).opacity)).toBe('0')
|
|
|
|
})
|
|
|
|
.toPass()
|
Refactor CSS; address some design issues (#9260)
- Implement https://github.com/enso-org/cloud-v2/issues/924
- Refactor all numbers out to CSS variables
- Implement some issues raised in the design meeting
- The columns selector now only contains *hidden* columns, rather than all of them.
- Unified opacity for active (100%), selectable and hovered (75%), selectable (50%) and disabled (30%)
- Easily configurable if we want to change it in the future, so the specific values don't matter too much for now.
- Always show asset right panel if it is enabled - display placeholder text if <1 or >1 asset is selected
- Hide docs icon that was in the top right assets menubar (next to the gear icon for asset settings) (as backend functionality has yet to be implemented)
- Clicking a user in the "Shared with" column now adds them to the search as `owner:<username>`
- Add a gap between adjacent rows. This makes each row more visually distinct when many rows are selected
- Center the left column (the first column) of the context menu below the mouse, rather than centering the entire context menu.
- Fix regressions caused by CSS refactor
- Make keyboard selection indicator for asset rows rounded again
- Other misc. fixes and improvements
- Slightly modified styling of chat reaction bar
- Hide the row containing the "New Project" button in the cloud drive, when not in the "Home" drive tab
- Animate rotation of column sort arrow when clicking on a column to change the sort order
- Consistent duration of arrow rotation animation for folder arrows, column sort arrows, chat thread list arrows
- Consistent icon for sort arrow for folders and the chat thread list
- Minor adjustment of styles for optional properties in the Data Link input
Not included in this PR:
- Custom (HTML) scrollbars for consistency across all browsers and all OSes (except perhaps touchscreens)
- Potentially time-consuming to look for a library (and not quite trivial to implement ourselves)
- Columns sliding left as they expand and right as they collapse
- Also non-trivial, especially when taking into account horizontal scrolling.
- Fixing styles to closer resemble Figma design
- As (kinda) mentioned in the meeting - ideally it should be pixel perfect, *but* value consistency with other spacings, opacities etc. over being 100% pixel-perfect
- However, it has *partly* been done - mostly for the home page. It's entirely possible that changes made afterwards broke the spacing again though.
# Important Notes
None
2024-03-13 13:32:05 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-20 19:19:01 +03:00
|
|
|
/** A test assertion to confirm that the element is not fully transparent. */
|
|
|
|
export async function expectNotOpacity0(locator: test.Locator) {
|
|
|
|
await test.test.step('Expect not `opacity: 0`', async () => {
|
|
|
|
await test
|
|
|
|
.expect(async () => {
|
|
|
|
test.expect(await locator.evaluate((el) => getComputedStyle(el).opacity)).not.toBe('0')
|
|
|
|
})
|
|
|
|
.toPass()
|
2024-01-31 14:35:41 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ==========================
|
|
|
|
// === Keyboard utilities ===
|
|
|
|
// ==========================
|
|
|
|
|
|
|
|
/** `Meta` (`Cmd`) on macOS, and `Control` on all other platforms. */
|
|
|
|
export async function modModifier(page: test.Page) {
|
|
|
|
let userAgent = ''
|
|
|
|
await test.test.step('Detect browser OS', async () => {
|
|
|
|
userAgent = await page.evaluate(() => navigator.userAgent)
|
|
|
|
})
|
|
|
|
return /\bMac OS\b/i.test(userAgent) ? 'Meta' : 'Control'
|
|
|
|
}
|
|
|
|
|
2024-10-11 21:23:02 +03:00
|
|
|
/**
|
|
|
|
* Press a key, replacing the text `Mod` with `Meta` (`Cmd`) on macOS, and `Control`
|
|
|
|
* on all other platforms.
|
|
|
|
*/
|
2024-01-31 14:35:41 +03:00
|
|
|
export async function press(page: test.Page, keyOrShortcut: string) {
|
2024-06-20 19:19:01 +03:00
|
|
|
await test.test.step(`Press '${keyOrShortcut}'`, async () => {
|
|
|
|
if (/\bMod\b|\bDelete\b/.test(keyOrShortcut)) {
|
|
|
|
let userAgent = ''
|
|
|
|
await test.test.step('Detect browser OS', async () => {
|
|
|
|
userAgent = await page.evaluate(() => navigator.userAgent)
|
|
|
|
})
|
|
|
|
const isMacOS = /\bMac OS\b/i.test(userAgent)
|
|
|
|
const ctrlKey = isMacOS ? 'Meta' : 'Control'
|
|
|
|
const deleteKey = isMacOS ? 'Backspace' : 'Delete'
|
|
|
|
const shortcut = keyOrShortcut.replace(/\bMod\b/, ctrlKey).replace(/\bDelete\b/, deleteKey)
|
|
|
|
await page.keyboard.press(shortcut)
|
|
|
|
} else {
|
|
|
|
await page.keyboard.press(keyOrShortcut)
|
|
|
|
}
|
|
|
|
})
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
2024-09-27 10:05:10 +03:00
|
|
|
// ===============================
|
|
|
|
// === Miscellaneous utilities ===
|
|
|
|
// ===============================
|
2024-01-31 14:35:41 +03:00
|
|
|
|
|
|
|
/** Perform a successful login. */
|
|
|
|
export async function login(
|
2024-06-25 19:56:14 +03:00
|
|
|
{ page, setupAPI }: MockParams,
|
2024-01-31 14:35:41 +03:00
|
|
|
email = 'email@example.com',
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
password = VALID_PASSWORD,
|
|
|
|
first = true,
|
2024-01-31 14:35:41 +03:00
|
|
|
) {
|
2024-06-20 19:19:01 +03:00
|
|
|
await test.test.step('Login', async () => {
|
2024-11-15 15:12:55 +03:00
|
|
|
const url = new URL(page.url())
|
|
|
|
|
|
|
|
if (url.pathname !== '/login') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-06-20 19:19:01 +03:00
|
|
|
await locateEmailInput(page).fill(email)
|
|
|
|
await locatePasswordInput(page).fill(password)
|
|
|
|
await locateLoginButton(page).click()
|
2024-11-15 15:12:55 +03:00
|
|
|
|
2024-09-27 10:05:10 +03:00
|
|
|
await test.expect(page.getByText(TEXT.loadingAppMessage)).not.toBeVisible()
|
2024-11-15 15:12:55 +03:00
|
|
|
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
if (first) {
|
2024-08-28 20:51:31 +03:00
|
|
|
await passAgreementsDialog({ page, setupAPI })
|
2024-09-27 10:05:10 +03:00
|
|
|
await test.expect(page.getByText(TEXT.loadingAppMessage)).not.toBeVisible()
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
}
|
2024-06-20 19:19:01 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
/** Reload. */
|
|
|
|
export async function reload({ page }: MockParams) {
|
|
|
|
await test.test.step('Reload', async () => {
|
|
|
|
await page.reload()
|
2024-09-27 10:05:10 +03:00
|
|
|
await test.expect(page.getByText(TEXT.loadingAppMessage)).not.toBeVisible()
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Logout and then login again. */
|
|
|
|
export async function relog(
|
2024-06-25 19:56:14 +03:00
|
|
|
{ page, setupAPI }: MockParams,
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
email = 'email@example.com',
|
|
|
|
password = VALID_PASSWORD,
|
|
|
|
) {
|
|
|
|
await test.test.step('Relog', async () => {
|
2024-09-27 10:05:10 +03:00
|
|
|
await page.getByLabel(TEXT.userMenuLabel).locator('visible=true').click()
|
|
|
|
await page
|
|
|
|
.getByRole('button', { name: TEXT.signOutShortcut })
|
|
|
|
.getByText(TEXT.signOutShortcut)
|
|
|
|
.click()
|
2024-06-25 19:56:14 +03:00
|
|
|
await login({ page, setupAPI }, email, password, false)
|
2024-06-20 19:19:01 +03:00
|
|
|
})
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** A placeholder date for visual regression testing. */
|
|
|
|
const MOCK_DATE = Number(new Date('01/23/45 01:23:45'))
|
|
|
|
|
|
|
|
/** Parameters for {@link mockDate}. */
|
|
|
|
interface MockParams {
|
2024-02-07 14:26:59 +03:00
|
|
|
readonly page: test.Page
|
2024-06-25 19:56:14 +03:00
|
|
|
readonly setupAPI?: apiModule.SetupAPI | undefined
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Replace `Date` with a version that returns a fixed time. */
|
|
|
|
async function mockDate({ page }: MockParams) {
|
|
|
|
// https://github.com/microsoft/playwright/issues/6347#issuecomment-1085850728
|
2024-06-20 19:19:01 +03:00
|
|
|
await test.test.step('Mock Date', async () => {
|
|
|
|
await page.addInitScript(`{
|
2024-01-31 14:35:41 +03:00
|
|
|
Date = class extends Date {
|
|
|
|
constructor(...args) {
|
|
|
|
if (args.length === 0) {
|
|
|
|
super(${MOCK_DATE});
|
|
|
|
} else {
|
|
|
|
super(...args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const __DateNowOffset = ${MOCK_DATE} - Date.now();
|
|
|
|
const __DateNow = Date.now;
|
|
|
|
Date.now = () => __DateNow() + __DateNowOffset;
|
|
|
|
}`)
|
2024-06-20 19:19:01 +03:00
|
|
|
})
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
2024-08-28 20:51:31 +03:00
|
|
|
/** Pass the Agreements dialog. */
|
|
|
|
export async function passAgreementsDialog({ page }: MockParams) {
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
await test.test.step('Accept Terms and Conditions', async () => {
|
2024-08-28 20:51:31 +03:00
|
|
|
await page.waitForSelector('#agreements-modal')
|
2024-09-27 10:05:10 +03:00
|
|
|
await page
|
|
|
|
.getByRole('group', { name: TEXT.licenseAgreementCheckbox })
|
|
|
|
.getByText(TEXT.licenseAgreementCheckbox)
|
|
|
|
.click()
|
|
|
|
await page
|
|
|
|
.getByRole('group', { name: TEXT.privacyPolicyCheckbox })
|
|
|
|
.getByText(TEXT.privacyPolicyCheckbox)
|
|
|
|
.click()
|
Offline Mode Support (#10317)
#### Tl;dr
Closes: enso-org/cloud-v2#1283
This PR significantly reimplements Offline mode
<details><summary>Demo Presentation</summary>
<p>
https://github.com/enso-org/enso/assets/61194245/752d0423-9c0a-43ba-91e3-4a6688f77034
</p>
</details>
---
#### Context:
Offline mode is one of the core features of the dashboard. Unfortunately, after adding new features and a few refactoring, we lost the ability to work offline.
This PR should bring this functionality back, with a few key differences:
1. We require users to sign in before using the dashboard even in local mode.
2. Once a user is logged in, we allow him to work with local files
3. If a user closes the dashboard, and then open it, he can continue using it in offline mode
#### This Change:
What does this change do in the larger context? Specific details to highlight for review:
1. Reimplements `<AuthProvider />` functionality, now it implemented on top of `<Suspense />` and ReactQuery
2. Reimplements Backend module flow, now remote backend is always created, You no longer need to check if the RemoteBackend is present
3. Introduces new `<Suspense />` component, which is aware of offline status
4. Introduce new offline-related hooks
5. Add a banner to the form if it's unable to submit it offline
6. Refactor `InviteUserDialog` to the new `<Form />` component
7. Fixes redirect bug when the app doesn't redirect a user to the dashboard after logging in
8. Fixes strange behavior when `/users/me` could stuck into infinite refetch
9. Redesign the Cloud table for offline mode.
10. Adds blocking UI dialog when a user clicks "log out" button
#### Test Plan:
This PR requires thorough QA on the login flow across the browser and IDE. All redirect logic must stay unchanged.
---
2024-06-21 10:14:40 +03:00
|
|
|
await page.getByRole('button', { name: 'Accept' }).click()
|
|
|
|
})
|
2024-05-23 10:53:55 +03:00
|
|
|
}
|
|
|
|
|
2024-01-31 14:35:41 +03:00
|
|
|
export const mockApi = apiModule.mockApi
|
|
|
|
|
|
|
|
/** Set up all mocks, without logging in. */
|
2024-07-16 12:55:45 +03:00
|
|
|
export function mockAll({ page, setupAPI }: MockParams) {
|
2024-11-15 15:12:55 +03:00
|
|
|
const actions = new LoginPageActions(page)
|
|
|
|
|
|
|
|
actions.step('Execute all mocks', async () => {
|
|
|
|
await Promise.all([
|
|
|
|
mockApi({ page, setupAPI }),
|
|
|
|
mockDate({ page, setupAPI }),
|
|
|
|
mockAllAnimations({ page }),
|
|
|
|
mockUnneededUrls({ page }),
|
|
|
|
])
|
|
|
|
|
2024-08-13 14:50:07 +03:00
|
|
|
await page.goto('/')
|
2024-06-20 19:19:01 +03:00
|
|
|
})
|
2024-11-15 15:12:55 +03:00
|
|
|
|
|
|
|
return actions
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Set up all mocks, and log in with dummy credentials. */
|
2024-11-15 15:12:55 +03:00
|
|
|
export function mockAllAndLogin({ page, setupAPI }: MockParams): DrivePageActions {
|
|
|
|
mockAll({ page, setupAPI })
|
|
|
|
|
|
|
|
const actions = new DrivePageActions(page)
|
|
|
|
|
|
|
|
actions.step('Login', async () => {
|
|
|
|
await login({ page, setupAPI })
|
|
|
|
})
|
|
|
|
|
|
|
|
return actions
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mock all animations.
|
|
|
|
*/
|
|
|
|
export async function mockAllAnimations({ page }: MockParams) {
|
|
|
|
await page.addInitScript({
|
|
|
|
content: `
|
|
|
|
window.DISABLE_ANIMATIONS = true;
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
document.documentElement.classList.add('disable-animations')
|
|
|
|
})
|
|
|
|
`,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mock unneeded URLs.
|
|
|
|
*/
|
|
|
|
export async function mockUnneededUrls({ page }: MockParams) {
|
|
|
|
const EULA_JSON = JSON.stringify(apiModule.EULA_JSON)
|
|
|
|
const PRIVACY_JSON = JSON.stringify(apiModule.PRIVACY_JSON)
|
|
|
|
|
|
|
|
return Promise.all([
|
|
|
|
page.route('https://*.ingest.sentry.io/api/*/envelope/*', async (route) => {
|
|
|
|
await route.fulfill()
|
|
|
|
}),
|
|
|
|
|
|
|
|
page.route('https://api.mapbox.com/mapbox-gl-js/*/mapbox-gl.css', async (route) => {
|
|
|
|
await route.fulfill({ contentType: 'text/css', body: '' })
|
|
|
|
}),
|
|
|
|
|
|
|
|
page.route('https://ensoanalytics.com/eula.json', async (route) => {
|
|
|
|
await route.fulfill({ contentType: 'text/json', body: EULA_JSON })
|
|
|
|
}),
|
|
|
|
|
|
|
|
page.route('https://ensoanalytics.com/privacy.json', async (route) => {
|
|
|
|
await route.fulfill({ contentType: 'text/json', body: PRIVACY_JSON })
|
|
|
|
}),
|
|
|
|
|
|
|
|
page.route('https://fonts.googleapis.com/css2*', async (route) => {
|
|
|
|
await route.fulfill({ contentType: 'text/css', body: '' })
|
|
|
|
}),
|
|
|
|
])
|
2024-07-16 12:55:45 +03:00
|
|
|
}
|
|
|
|
|
2024-10-11 21:23:02 +03:00
|
|
|
/**
|
|
|
|
* Set up all mocks, and log in with dummy credentials.
|
|
|
|
* @deprecated Prefer {@link mockAllAndLogin}.
|
|
|
|
*/
|
2024-07-16 12:55:45 +03:00
|
|
|
export async function mockAllAndLoginAndExposeAPI({ page, setupAPI }: MockParams) {
|
2024-06-20 19:19:01 +03:00
|
|
|
return await test.test.step('Execute all mocks and login', async () => {
|
2024-07-16 12:55:45 +03:00
|
|
|
const api = await mockApi({ page, setupAPI })
|
|
|
|
await mockDate({ page, setupAPI })
|
2024-08-13 14:50:07 +03:00
|
|
|
await page.goto('/')
|
2024-06-25 19:56:14 +03:00
|
|
|
await login({ page, setupAPI })
|
2024-07-16 12:55:45 +03:00
|
|
|
return api
|
2024-06-20 19:19:01 +03:00
|
|
|
})
|
2024-01-31 14:35:41 +03:00
|
|
|
}
|