Compare commits
9 Commits
main
...
pierremtb/
Author | SHA1 | Date | |
---|---|---|---|
445e18642f | |||
cc2ae13241 | |||
980495c94b | |||
cdcee56ae2 | |||
4067ff4be8 | |||
2570713f6d | |||
42cd72c9bb | |||
e53de2952a | |||
2a07f5addc |
@ -29,11 +29,11 @@ test.describe('Electron app header tests', () => {
|
|||||||
test(
|
test(
|
||||||
'User settings has correct shortcut',
|
'User settings has correct shortcut',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page, toolbar }, testInfo) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
// Open the user sidebar menu.
|
// Open the user sidebar menu.
|
||||||
await page.getByTestId('user-sidebar-toggle').click()
|
await toolbar.userSidebarButton.click()
|
||||||
|
|
||||||
// No space after "User settings" since it's textContent.
|
// No space after "User settings" since it's textContent.
|
||||||
const text =
|
const text =
|
||||||
|
53
e2e/playwright/auth.spec.ts
Normal file
53
e2e/playwright/auth.spec.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
|
// test file is for testing auth functionality
|
||||||
|
test.describe('Authentication tests', () => {
|
||||||
|
test(
|
||||||
|
`The user can sign out and back in`,
|
||||||
|
{ tag: ['@electron'] },
|
||||||
|
async ({ page, homePage, signInPage, toolbar, tronApp }) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.projectSection.waitFor()
|
||||||
|
|
||||||
|
await test.step('Click on sign out and expect sign in page', async () => {
|
||||||
|
await toolbar.userSidebarButton.click()
|
||||||
|
await toolbar.signOutButton.click()
|
||||||
|
await expect(signInPage.signInButton).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Click on sign in and cancel, click again and expect different code', async () => {
|
||||||
|
await signInPage.signInButton.click()
|
||||||
|
await expect(signInPage.userCode).toBeVisible()
|
||||||
|
const firstUserCode = await signInPage.userCode.textContent()
|
||||||
|
await signInPage.cancelSignInButton.click()
|
||||||
|
await expect(signInPage.signInButton).toBeVisible()
|
||||||
|
|
||||||
|
await signInPage.signInButton.click()
|
||||||
|
await expect(signInPage.userCode).toBeVisible()
|
||||||
|
const secondUserCode = await signInPage.userCode.textContent()
|
||||||
|
expect(secondUserCode).not.toEqual(firstUserCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Press back button and remain on home page', async () => {
|
||||||
|
await page.goBack()
|
||||||
|
await expect(homePage.projectSection).not.toBeVisible()
|
||||||
|
await expect(signInPage.signInButton).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Sign in, activate, and expect home page', async () => {
|
||||||
|
await signInPage.signInButton.click()
|
||||||
|
await expect(signInPage.userCode).toBeVisible()
|
||||||
|
const userCode = await signInPage.userCode.textContent()
|
||||||
|
expect(userCode).not.toBeNull()
|
||||||
|
await signInPage.verifyAndConfirmAuth(userCode!)
|
||||||
|
|
||||||
|
// Longer timeout than usual here for the wait on home page
|
||||||
|
await expect(homePage.projectSection).toBeVisible({ timeout: 10000 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
@ -18,6 +18,7 @@ import type { Settings } from '@rust/kcl-lib/bindings/Settings'
|
|||||||
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||||
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
||||||
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
|
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
|
||||||
|
import { SignInPageFixture } from '@e2e/playwright/fixtures/signInPageFixture'
|
||||||
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
||||||
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||||
|
|
||||||
@ -66,6 +67,7 @@ export interface Fixtures {
|
|||||||
toolbar: ToolbarFixture
|
toolbar: ToolbarFixture
|
||||||
scene: SceneFixture
|
scene: SceneFixture
|
||||||
homePage: HomePageFixture
|
homePage: HomePageFixture
|
||||||
|
signInPage: SignInPageFixture
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ElectronZoo {
|
export class ElectronZoo {
|
||||||
@ -387,6 +389,9 @@ const fixturesBasedOnProcessEnvPlatform = {
|
|||||||
homePage: async ({ page }: { page: Page }, use: FnUse) => {
|
homePage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
await use(new HomePageFixture(page))
|
await use(new HomePageFixture(page))
|
||||||
},
|
},
|
||||||
|
signInPage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
|
await use(new SignInPageFixture(page))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.PLATFORM === 'web') {
|
if (process.env.PLATFORM === 'web') {
|
||||||
|
48
e2e/playwright/fixtures/signInPageFixture.ts
Normal file
48
e2e/playwright/fixtures/signInPageFixture.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
import { secrets } from '@e2e/playwright/secrets'
|
||||||
|
|
||||||
|
export class SignInPageFixture {
|
||||||
|
public page: Page
|
||||||
|
|
||||||
|
signInButton!: Locator
|
||||||
|
cancelSignInButton!: Locator
|
||||||
|
userCode!: Locator
|
||||||
|
|
||||||
|
apiBaseUrl!: string
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
this.page = page
|
||||||
|
|
||||||
|
this.signInButton = this.page.getByTestId('sign-in-button')
|
||||||
|
this.cancelSignInButton = this.page.getByTestId('cancel-sign-in-button')
|
||||||
|
this.userCode = this.page.getByTestId('sign-in-user-code')
|
||||||
|
|
||||||
|
// TODO: set this thru env var
|
||||||
|
this.apiBaseUrl = 'https://api.dev.zoo.dev'
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyAndConfirmAuth(userCode: string) {
|
||||||
|
// Device flow: stolen from the tauri days
|
||||||
|
// https://github.com/KittyCAD/modeling-app/blob/d916c7987452e480719004e6d11fd2e595c7d0eb/e2e/tauri/specs/app.spec.ts#L19
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${secrets.token}`,
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
const verifyUrl = `${this.apiBaseUrl}/oauth2/device/verify?user_code=${userCode}`
|
||||||
|
console.log(`GET ${verifyUrl}`)
|
||||||
|
const vr = await fetch(verifyUrl, { headers })
|
||||||
|
console.log(vr.status)
|
||||||
|
|
||||||
|
// Device flow: confirm
|
||||||
|
const confirmUrl = `${this.apiBaseUrl}/oauth2/device/confirm`
|
||||||
|
const data = JSON.stringify({ user_code: userCode })
|
||||||
|
console.log(`POST ${confirmUrl} ${data}`)
|
||||||
|
const cr = await fetch(confirmUrl, {
|
||||||
|
headers,
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
})
|
||||||
|
console.log(cr.status)
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,9 @@ export class ToolbarFixture {
|
|||||||
gizmo!: Locator
|
gizmo!: Locator
|
||||||
gizmoDisabled!: Locator
|
gizmoDisabled!: Locator
|
||||||
loadButton!: Locator
|
loadButton!: Locator
|
||||||
|
/** User button for the user sidebar menu */
|
||||||
|
userSidebarButton!: Locator
|
||||||
|
signOutButton!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
@ -82,6 +85,9 @@ export class ToolbarFixture {
|
|||||||
// element or two different elements can represent these states.
|
// element or two different elements can represent these states.
|
||||||
this.gizmo = page.getByTestId('gizmo')
|
this.gizmo = page.getByTestId('gizmo')
|
||||||
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
|
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
|
||||||
|
|
||||||
|
this.userSidebarButton = page.getByTestId('user-sidebar-toggle')
|
||||||
|
this.signOutButton = page.getByTestId('user-sidebar-sign-out')
|
||||||
}
|
}
|
||||||
|
|
||||||
get logoLink() {
|
get logoLink() {
|
||||||
|
@ -331,6 +331,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
test('Avatar text updates depending on image load success', async ({
|
test('Avatar text updates depending on image load success', async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
toolbar,
|
||||||
homePage,
|
homePage,
|
||||||
tronApp,
|
tronApp,
|
||||||
}) => {
|
}) => {
|
||||||
@ -362,7 +363,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Test that the text in this step is correct
|
// Test that the text in this step is correct
|
||||||
const avatarLocator = page.getByTestId('user-sidebar-toggle').locator('img')
|
const avatarLocator = toolbar.userSidebarButton.locator('img')
|
||||||
const onboardingOverlayLocator = page
|
const onboardingOverlayLocator = page
|
||||||
.getByTestId('onboarding-content')
|
.getByTestId('onboarding-content')
|
||||||
.locator('div')
|
.locator('div')
|
||||||
@ -404,6 +405,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
test("Avatar text doesn't mention avatar when no avatar", async ({
|
test("Avatar text doesn't mention avatar when no avatar", async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
toolbar,
|
||||||
homePage,
|
homePage,
|
||||||
tronApp,
|
tronApp,
|
||||||
}) => {
|
}) => {
|
||||||
@ -435,7 +437,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Test that the text in this step is correct
|
// Test that the text in this step is correct
|
||||||
const sidebar = page.getByTestId('user-sidebar-toggle')
|
const sidebar = toolbar.userSidebarButton
|
||||||
const avatar = sidebar.locator('img')
|
const avatar = sidebar.locator('img')
|
||||||
const onboardingOverlayLocator = page
|
const onboardingOverlayLocator = page
|
||||||
.getByTestId('onboarding-content')
|
.getByTestId('onboarding-content')
|
||||||
@ -464,6 +466,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
test('Restarting onboarding on desktop takes one attempt', async ({
|
test('Restarting onboarding on desktop takes one attempt', async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
toolbar,
|
||||||
tronApp,
|
tronApp,
|
||||||
}) => {
|
}) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
test.fixme(orRunWhenFullSuiteEnabled())
|
||||||
@ -502,7 +505,7 @@ test('Restarting onboarding on desktop takes one attempt', async ({
|
|||||||
.filter({ hasText: 'Tutorial Project 00' })
|
.filter({ hasText: 'Tutorial Project 00' })
|
||||||
const tutorialModalText = page.getByText('Welcome to Design Studio!')
|
const tutorialModalText = page.getByText('Welcome to Design Studio!')
|
||||||
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
|
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
|
||||||
const userMenuButton = page.getByTestId('user-sidebar-toggle')
|
const userMenuButton = toolbar.userSidebarButton
|
||||||
const userMenuSettingsButton = page.getByRole('button', {
|
const userMenuSettingsButton = page.getByRole('button', {
|
||||||
name: 'User settings',
|
name: 'User settings',
|
||||||
})
|
})
|
||||||
|
@ -1962,13 +1962,13 @@ test(
|
|||||||
test(
|
test(
|
||||||
'Settings persist across restarts',
|
'Settings persist across restarts',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ page, scene, cmdBar }, testInfo) => {
|
async ({ page, toolbar }, testInfo) => {
|
||||||
await test.step('We can change a user setting like theme', async () => {
|
await test.step('We can change a user setting like theme', async () => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
await page.getByTestId('user-sidebar-toggle').click()
|
await toolbar.userSidebarButton.click()
|
||||||
|
|
||||||
await page.getByTestId('user-settings').click()
|
await page.getByTestId('user-settings').click()
|
||||||
|
|
||||||
@ -1995,7 +1995,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'Original project name persist after onboarding',
|
'Original project name persist after onboarding',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page, toolbar }, testInfo) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
test.fixme(orRunWhenFullSuiteEnabled())
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
@ -2007,7 +2007,7 @@ test(
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Should go through onboarding', async () => {
|
await test.step('Should go through onboarding', async () => {
|
||||||
await page.getByTestId('user-sidebar-toggle').click()
|
await toolbar.userSidebarButton.click()
|
||||||
await page.getByTestId('user-settings').click()
|
await page.getByTestId('user-settings').click()
|
||||||
await page.getByRole('button', { name: 'Replay Onboarding' }).click()
|
await page.getByRole('button', { name: 'Replay Onboarding' }).click()
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ export const Auth = ({ children }: React.PropsWithChildren) => {
|
|||||||
const isLoggingIn = authState.matches('checkIfLoggedIn')
|
const isLoggingIn = authState.matches('checkIfLoggedIn')
|
||||||
|
|
||||||
return isLoggingIn ? (
|
return isLoggingIn ? (
|
||||||
<Loading>
|
<Loading className="h-screen">
|
||||||
<span data-testid="initial-load">Loading Design Studio...</span>
|
<span data-testid="initial-load">Loading Design Studio...</span>
|
||||||
</Loading>
|
</Loading>
|
||||||
) : (
|
) : (
|
||||||
|
@ -430,7 +430,7 @@ export const EngineStream = (props: {
|
|||||||
{![EngineStreamState.Playing, EngineStreamState.Paused].some(
|
{![EngineStreamState.Playing, EngineStreamState.Paused].some(
|
||||||
(s) => s === engineStreamState.value
|
(s) => s === engineStreamState.value
|
||||||
) && (
|
) && (
|
||||||
<Loading dataTestId="loading-engine" className="fixed inset-0">
|
<Loading dataTestId="loading-engine" className="fixed inset-0 h-screen">
|
||||||
Connecting to engine
|
Connecting to engine
|
||||||
</Loading>
|
</Loading>
|
||||||
)}
|
)}
|
||||||
|
@ -111,7 +111,7 @@ const Loading = ({ children, className, dataTestId }: LoadingProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`body-bg flex flex-col items-center justify-center h-screen ${colorClass} ${className}`}
|
className={`body-bg flex flex-col items-center justify-center ${colorClass} ${className}`}
|
||||||
data-testid={dataTestId ? dataTestId : 'loading'}
|
data-testid={dataTestId ? dataTestId : 'loading'}
|
||||||
>
|
>
|
||||||
{isUnrecoverableError ? (
|
{isUnrecoverableError ? (
|
||||||
|
@ -15,6 +15,7 @@ export function useAuthNavigation() {
|
|||||||
|
|
||||||
// Subscribe to the auth state of the app and navigate accordingly.
|
// Subscribe to the auth state of the app and navigate accordingly.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log('authState', authState.value)
|
||||||
if (
|
if (
|
||||||
authState.matches('loggedIn') &&
|
authState.matches('loggedIn') &&
|
||||||
location.pathname.includes(PATHS.SIGN_IN)
|
location.pathname.includes(PATHS.SIGN_IN)
|
||||||
@ -26,5 +27,5 @@ export function useAuthNavigation() {
|
|||||||
) {
|
) {
|
||||||
navigate(PATHS.SIGN_IN)
|
navigate(PATHS.SIGN_IN)
|
||||||
}
|
}
|
||||||
}, [authState])
|
}, [authState, location.pathname])
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,7 @@ const Home = () => {
|
|||||||
className="flex-1 overflow-y-auto pr-2 pb-24"
|
className="flex-1 overflow-y-auto pr-2 pb-24"
|
||||||
>
|
>
|
||||||
{state?.matches('Reading projects') ? (
|
{state?.matches('Reading projects') ? (
|
||||||
<Loading>Loading your Projects...</Loading>
|
<Loading className="h-screen">Loading your Projects...</Loading>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{searchResults.length > 0 ? (
|
{searchResults.length > 0 ? (
|
||||||
|
@ -76,6 +76,11 @@ const SignIn = () => {
|
|||||||
authActor.send({ type: 'Log in', token })
|
authActor.send({ type: 'Log in', token })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cancelSignIn = async () => {
|
||||||
|
authActor.send({ type: 'Log out' })
|
||||||
|
setUserCode('')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main
|
<main
|
||||||
className="bg-primary h-screen grid place-items-stretch m-0 p-2"
|
className="bg-primary h-screen grid place-items-stretch m-0 p-2"
|
||||||
@ -128,7 +133,10 @@ const SignIn = () => {
|
|||||||
<p className="text-xs">
|
<p className="text-xs">
|
||||||
You should see the following code in your browser
|
You should see the following code in your browser
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg font-bold inline-flex gap-1">
|
<p
|
||||||
|
className="text-lg font-bold inline-flex gap-1"
|
||||||
|
data-testid="sign-in-user-code"
|
||||||
|
>
|
||||||
{userCode.split('').map((char, i) => (
|
{userCode.split('').map((char, i) => (
|
||||||
<span
|
<span
|
||||||
key={i}
|
key={i}
|
||||||
@ -141,6 +149,17 @@ const SignIn = () => {
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</p>
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={toSync(cancelSignIn, reportRejection)}
|
||||||
|
className={
|
||||||
|
'm-0 mt-8 w-fit flex gap-4 items-center px-3 py-1 ' +
|
||||||
|
'!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
|
||||||
|
}
|
||||||
|
data-testid="cancel-sign-in-button"
|
||||||
|
>
|
||||||
|
<CustomIcon name="arrowLeft" className="w-6 h-6" />
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -187,7 +206,7 @@ const SignIn = () => {
|
|||||||
'm-0 mt-8 flex gap-4 items-center px-3 py-1 ' +
|
'm-0 mt-8 flex gap-4 items-center px-3 py-1 ' +
|
||||||
'!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
|
'!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
|
||||||
}
|
}
|
||||||
data-testid="sign-in-button"
|
data-testid="view-sample-button"
|
||||||
>
|
>
|
||||||
View this sample
|
View this sample
|
||||||
<CustomIcon name="arrowRight" className="w-6 h-6" />
|
<CustomIcon name="arrowRight" className="w-6 h-6" />
|
||||||
|
Reference in New Issue
Block a user