mirror of
https://github.com/nextcloud/server.git
synced 2025-04-13 21:09:42 +00:00
test: make cypress run in secure context and add WebAuthn tests
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
a243e9cfbb
commit
45cfaa1b3b
9 changed files with 288 additions and 106 deletions
|
@ -35,6 +35,9 @@ module.exports = {
|
|||
jsdoc: {
|
||||
mode: 'typescript',
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {}, // this loads <rootdir>/tsconfig.json to eslint
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
// Allow any in tests
|
||||
|
@ -43,6 +46,6 @@ module.exports = {
|
|||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -88,6 +88,12 @@ export const startNextcloud = async function(branch: string = getCurrentGitBranc
|
|||
Type: 'tmpfs',
|
||||
ReadOnly: false,
|
||||
}],
|
||||
PortBindings: {
|
||||
'80/tcp': [{
|
||||
HostIP: '0.0.0.0',
|
||||
HostPort: '8083',
|
||||
}],
|
||||
},
|
||||
},
|
||||
Env: [
|
||||
`BRANCH=${branch}`,
|
||||
|
@ -242,11 +248,15 @@ export const getContainerIP = async function(
|
|||
while (ip === '' && tries < 10) {
|
||||
tries++
|
||||
|
||||
await container.inspect(function(err, data) {
|
||||
container.inspect(function(err, data) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
ip = data?.NetworkSettings?.IPAddress || ''
|
||||
if (data?.HostConfig.PortBindings?.['80/tcp']?.[0]?.HostPort) {
|
||||
ip = `localhost:${data.HostConfig.PortBindings['80/tcp'][0].HostPort}`
|
||||
} else {
|
||||
ip = data?.NetworkSettings?.IPAddress || ''
|
||||
}
|
||||
})
|
||||
|
||||
if (ip !== '') {
|
||||
|
|
|
@ -14,34 +14,25 @@ type SetupInfo = {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param user
|
||||
* @param fileName
|
||||
* @param domain
|
||||
* @param requesttoken
|
||||
* @param metadata
|
||||
*/
|
||||
function setMetadata(user: User, fileName: string, requesttoken: string, metadata: object) {
|
||||
cy.url().then(url => {
|
||||
const hostname = new URL(url).hostname
|
||||
cy.request({
|
||||
method: 'PROPPATCH',
|
||||
url: `http://${hostname}/remote.php/dav/files/${user.userId}/${fileName}`,
|
||||
auth: { user: user.userId, pass: user.password },
|
||||
headers: {
|
||||
requesttoken,
|
||||
},
|
||||
body: `<?xml version="1.0"?>
|
||||
<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns">
|
||||
<d:set>
|
||||
<d:prop>
|
||||
${Object.entries(metadata).map(([key, value]) => `<${key}>${value}</${key}>`).join('\n')}
|
||||
</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>`,
|
||||
})
|
||||
const base = Cypress.config('baseUrl')!.replace(/\/index\.php\/?/, '')
|
||||
cy.request({
|
||||
method: 'PROPPATCH',
|
||||
url: `${base}/remote.php/dav/files/${user.userId}/${fileName}`,
|
||||
auth: { user: user.userId, pass: user.password },
|
||||
headers: {
|
||||
requesttoken,
|
||||
},
|
||||
body: `<?xml version="1.0"?>
|
||||
<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns">
|
||||
<d:set>
|
||||
<d:prop>
|
||||
${Object.entries(metadata).map(([key, value]) => `<${key}>${value}</${key}>`).join('\n')}
|
||||
</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>`,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,9 +15,6 @@ describe('Files user credentials', { testIsolation: true }, () => {
|
|||
let user2: User
|
||||
let storageUser: User
|
||||
|
||||
beforeEach(() => {
|
||||
})
|
||||
|
||||
before(() => {
|
||||
cy.runOccCommand('app:enable files_external')
|
||||
|
||||
|
@ -43,8 +40,10 @@ describe('Files user credentials', { testIsolation: true }, () => {
|
|||
})
|
||||
|
||||
it('Create a user storage with user credentials', () => {
|
||||
const url = Cypress.config('baseUrl') + '/remote.php/dav/files/' + storageUser.userId
|
||||
createStorageWithConfig(storageUser.userId, StorageBackend.DAV, AuthBackend.UserProvided, { host: url.replace('index.php/', ''), secure: 'false' })
|
||||
// Its not the public server address but the address so the server itself can connect to it
|
||||
const base = 'http://localhost'
|
||||
const host = `${base}/remote.php/dav/files/${storageUser.userId}`
|
||||
createStorageWithConfig(storageUser.userId, StorageBackend.DAV, AuthBackend.UserProvided, { host, secure: 'false' })
|
||||
|
||||
cy.login(user1)
|
||||
cy.visit('/apps/files/extstoragemounts')
|
||||
|
@ -72,6 +71,7 @@ describe('Files user credentials', { testIsolation: true }, () => {
|
|||
|
||||
// Auth dialog should be closed and the set credentials button should be gone
|
||||
cy.get('@authDialog').should('not.exist', { timeout: 2000 })
|
||||
|
||||
getActionEntryForFile(storageUser.userId, ACTION_CREDENTIALS_EXTERNAL_STORAGE).should('not.exist')
|
||||
|
||||
// Finally, the storage should be accessible
|
||||
|
@ -81,8 +81,10 @@ describe('Files user credentials', { testIsolation: true }, () => {
|
|||
})
|
||||
|
||||
it('Create a user storage with GLOBAL user credentials', () => {
|
||||
const url = Cypress.config('baseUrl') + '/remote.php/dav/files/' + storageUser.userId
|
||||
createStorageWithConfig('storage1', StorageBackend.DAV, AuthBackend.UserGlobalAuth, { host: url.replace('index.php/', ''), secure: 'false' })
|
||||
// Its not the public server address but the address so the server itself can connect to it
|
||||
const base = 'http://localhost'
|
||||
const host = `${base}/remote.php/dav/files/${storageUser.userId}`
|
||||
createStorageWithConfig('storage1', StorageBackend.DAV, AuthBackend.UserGlobalAuth, { host, secure: 'false' })
|
||||
|
||||
cy.login(user2)
|
||||
cy.visit('/apps/files/extstoragemounts')
|
||||
|
@ -119,8 +121,10 @@ describe('Files user credentials', { testIsolation: true }, () => {
|
|||
})
|
||||
|
||||
it('Create another user storage while reusing GLOBAL user credentials', () => {
|
||||
const url = Cypress.config('baseUrl') + '/remote.php/dav/files/' + storageUser.userId
|
||||
createStorageWithConfig('storage2', StorageBackend.DAV, AuthBackend.UserGlobalAuth, { host: url.replace('index.php/', ''), secure: 'false' })
|
||||
// Its not the public server address but the address so the server itself can connect to it
|
||||
const base = 'http://localhost'
|
||||
const host = `${base}/remote.php/dav/files/${storageUser.userId}`
|
||||
createStorageWithConfig('storage2', StorageBackend.DAV, AuthBackend.UserGlobalAuth, { host, secure: 'false' })
|
||||
|
||||
cy.login(user2)
|
||||
cy.visit('/apps/files/extstoragemounts')
|
||||
|
|
|
@ -59,7 +59,6 @@ describe('Versions restoration', () => {
|
|||
})
|
||||
|
||||
it('Does not work without delete permission through direct API access', () => {
|
||||
let hostname: string
|
||||
let fileId: string|undefined
|
||||
let versionId: string|undefined
|
||||
|
||||
|
@ -68,24 +67,30 @@ describe('Versions restoration', () => {
|
|||
navigateToFolder(folderName)
|
||||
openVersionsPanel(randomFilePath)
|
||||
|
||||
cy.url().then(url => { hostname = new URL(url).hostname })
|
||||
getRowForFile(randomFileName).invoke('attr', 'data-cy-files-list-row-fileid').then(_fileId => { fileId = _fileId })
|
||||
cy.get('[data-files-versions-version]').eq(1).invoke('attr', 'data-files-versions-version').then(_versionId => { versionId = _versionId })
|
||||
getRowForFile(randomFileName)
|
||||
.should('be.visible')
|
||||
.invoke('attr', 'data-cy-files-list-row-fileid')
|
||||
.then(($fileId) => { fileId = $fileId })
|
||||
|
||||
cy.get('[data-files-versions-version]')
|
||||
.eq(1)
|
||||
.invoke('attr', 'data-files-versions-version')
|
||||
.then(($versionId) => { versionId = $versionId })
|
||||
|
||||
cy.logout()
|
||||
cy.then(() => {
|
||||
cy.logout()
|
||||
cy.request({
|
||||
const base = Cypress.config('baseUrl')!.replace(/\/index\.php\/?$/, '')
|
||||
return cy.request({
|
||||
method: 'DELETE',
|
||||
url: `${base}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
|
||||
auth: { user: recipient.userId, pass: recipient.password },
|
||||
headers: {
|
||||
cookie: '',
|
||||
},
|
||||
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
|
||||
failOnStatusCode: false,
|
||||
})
|
||||
.then(({ status }) => {
|
||||
expect(status).to.equal(403)
|
||||
})
|
||||
}).then(({ status }) => {
|
||||
expect(status).to.equal(403)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -52,31 +52,36 @@ describe('Versions download', () => {
|
|||
})
|
||||
|
||||
it('Does not work without download permission through direct API access', () => {
|
||||
let hostname: string
|
||||
let fileId: string|undefined
|
||||
let versionId: string|undefined
|
||||
|
||||
setupTestSharedFileFromUser(user, randomFileName, { download: false })
|
||||
.then(recipient => {
|
||||
.then((recipient) => {
|
||||
openVersionsPanel(randomFileName)
|
||||
|
||||
cy.url().then(url => { hostname = new URL(url).hostname })
|
||||
getRowForFile(randomFileName).invoke('attr', 'data-cy-files-list-row-fileid').then(_fileId => { fileId = _fileId })
|
||||
cy.get('[data-files-versions-version]').eq(1).invoke('attr', 'data-files-versions-version').then(_versionId => { versionId = _versionId })
|
||||
getRowForFile(randomFileName)
|
||||
.should('be.visible')
|
||||
.invoke('attr', 'data-cy-files-list-row-fileid')
|
||||
.then(($fileId) => { fileId = $fileId })
|
||||
|
||||
cy.get('[data-files-versions-version]')
|
||||
.eq(1)
|
||||
.invoke('attr', 'data-files-versions-version')
|
||||
.then(($versionId) => { versionId = $versionId })
|
||||
|
||||
cy.logout()
|
||||
cy.then(() => {
|
||||
cy.logout()
|
||||
cy.request({
|
||||
const base = Cypress.config('baseUrl')!.replace(/\/index\.php\/?$/, '')
|
||||
return cy.request({
|
||||
url: `${base}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
|
||||
auth: { user: recipient.userId, pass: recipient.password },
|
||||
headers: {
|
||||
cookie: '',
|
||||
},
|
||||
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
|
||||
failOnStatusCode: false,
|
||||
})
|
||||
.then(({ status }) => {
|
||||
expect(status).to.equal(403)
|
||||
})
|
||||
}).then(({ status }) => {
|
||||
expect(status).to.equal(403)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -69,10 +69,17 @@ describe('Versions naming', () => {
|
|||
})
|
||||
|
||||
context('without edit permission', () => {
|
||||
it('Does not show action', () => {
|
||||
setupTestSharedFileFromUser(user, randomFileName, { update: false })
|
||||
openVersionsPanel(randomFileName)
|
||||
let recipient: User
|
||||
|
||||
beforeEach(() => {
|
||||
setupTestSharedFileFromUser(user, randomFileName, { update: false })
|
||||
.then(($recipient) => {
|
||||
recipient = $recipient
|
||||
openVersionsPanel(randomFileName)
|
||||
})
|
||||
})
|
||||
|
||||
it('Does not show action', () => {
|
||||
cy.get('[data-files-versions-version]').eq(0).find('.action-item__menutoggle').should('not.exist')
|
||||
cy.get('[data-files-versions-version]').eq(0).get('[data-cy-version-action="label"]').should('not.exist')
|
||||
|
||||
|
@ -81,45 +88,45 @@ describe('Versions naming', () => {
|
|||
})
|
||||
|
||||
it('Does not work without update permission through direct API access', () => {
|
||||
let hostname: string
|
||||
let fileId: string|undefined
|
||||
let versionId: string|undefined
|
||||
|
||||
setupTestSharedFileFromUser(user, randomFileName, { update: false })
|
||||
.then(recipient => {
|
||||
openVersionsPanel(randomFileName)
|
||||
getRowForFile(randomFileName)
|
||||
.should('be.visible')
|
||||
.invoke('attr', 'data-cy-files-list-row-fileid')
|
||||
.then(($fileId) => { fileId = $fileId })
|
||||
|
||||
cy.url().then(url => { hostname = new URL(url).hostname })
|
||||
getRowForFile(randomFileName).invoke('attr', 'data-cy-files-list-row-fileid').then(_fileId => { fileId = _fileId })
|
||||
cy.get('[data-files-versions-version]').eq(1).invoke('attr', 'data-files-versions-version').then(_versionId => { versionId = _versionId })
|
||||
cy.get('[data-files-versions-version]')
|
||||
.eq(1)
|
||||
.invoke('attr', 'data-files-versions-version')
|
||||
.then(($versionId) => { versionId = $versionId })
|
||||
|
||||
cy.then(() => {
|
||||
cy.logout()
|
||||
cy.request({
|
||||
method: 'PROPPATCH',
|
||||
auth: { user: recipient.userId, pass: recipient.password },
|
||||
headers: {
|
||||
cookie: '',
|
||||
},
|
||||
body: `<?xml version="1.0"?>
|
||||
<d:propertyupdate xmlns:d="DAV:"
|
||||
xmlns:oc="http://owncloud.org/ns"
|
||||
xmlns:nc="http://nextcloud.org/ns"
|
||||
xmlns:ocs="http://open-collaboration-services.org/ns">
|
||||
<d:set>
|
||||
<d:prop>
|
||||
<nc:version-label>not authorized labeling</nc:version-label>
|
||||
</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>`,
|
||||
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
|
||||
failOnStatusCode: false,
|
||||
})
|
||||
.then(({ status }) => {
|
||||
expect(status).to.equal(403)
|
||||
})
|
||||
})
|
||||
cy.logout()
|
||||
cy.then(() => {
|
||||
const base = Cypress.config('baseUrl')!.replace(/index\.php\/?/, '')
|
||||
return cy.request({
|
||||
method: 'PROPPATCH',
|
||||
url: `${base}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
|
||||
auth: { user: recipient.userId, pass: recipient.password },
|
||||
headers: {
|
||||
cookie: '',
|
||||
},
|
||||
body: `<?xml version="1.0"?>
|
||||
<d:propertyupdate xmlns:d="DAV:"
|
||||
xmlns:oc="http://owncloud.org/ns"
|
||||
xmlns:nc="http://nextcloud.org/ns"
|
||||
xmlns:ocs="http://open-collaboration-services.org/ns">
|
||||
<d:set>
|
||||
<d:prop>
|
||||
<nc:version-label>not authorized labeling</nc:version-label>
|
||||
</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>`,
|
||||
failOnStatusCode: false,
|
||||
})
|
||||
}).then(({ status }) => {
|
||||
expect(status).to.equal(403)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -77,33 +77,38 @@ describe('Versions restoration', () => {
|
|||
})
|
||||
|
||||
it('Does not work without update permission through direct API access', () => {
|
||||
let hostname: string
|
||||
let fileId: string|undefined
|
||||
let versionId: string|undefined
|
||||
|
||||
setupTestSharedFileFromUser(user, randomFileName, { update: false })
|
||||
.then(recipient => {
|
||||
.then((recipient) => {
|
||||
openVersionsPanel(randomFileName)
|
||||
|
||||
cy.url().then(url => { hostname = new URL(url).hostname })
|
||||
getRowForFile(randomFileName).invoke('attr', 'data-cy-files-list-row-fileid').then(_fileId => { fileId = _fileId })
|
||||
cy.get('[data-files-versions-version]').eq(1).invoke('attr', 'data-files-versions-version').then(_versionId => { versionId = _versionId })
|
||||
getRowForFile(randomFileName)
|
||||
.should('be.visible')
|
||||
.invoke('attr', 'data-cy-files-list-row-fileid')
|
||||
.then(($fileId) => { fileId = $fileId })
|
||||
|
||||
cy.get('[data-files-versions-version]')
|
||||
.eq(1)
|
||||
.invoke('attr', 'data-files-versions-version')
|
||||
.then(($versionId) => { versionId = $versionId })
|
||||
|
||||
cy.logout()
|
||||
cy.then(() => {
|
||||
cy.logout()
|
||||
cy.request({
|
||||
const base = Cypress.config('baseUrl')!.replace(/\/index\.php\/?$/, '')
|
||||
return cy.request({
|
||||
method: 'MOVE',
|
||||
url: `${base}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
|
||||
auth: { user: recipient.userId, pass: recipient.password },
|
||||
headers: {
|
||||
cookie: '',
|
||||
Destination: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/restore/target`,
|
||||
Destination: `${base}}/remote.php/dav/versions/${recipient.userId}/restore/target`,
|
||||
},
|
||||
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`,
|
||||
failOnStatusCode: false,
|
||||
})
|
||||
.then(({ status }) => {
|
||||
expect(status).to.equal(403)
|
||||
})
|
||||
}).then(({ status }) => {
|
||||
expect(status).to.equal(403)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
152
cypress/e2e/login/webauth.cy.ts
Normal file
152
cypress/e2e/login/webauth.cy.ts
Normal file
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { User } from '@nextcloud/cypress'
|
||||
|
||||
interface IChromeVirtualAuthenticator {
|
||||
authenticatorId: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a virtual authenticator using chrome debug protocol
|
||||
*/
|
||||
async function createAuthenticator(): Promise<IChromeVirtualAuthenticator> {
|
||||
await Cypress.automation('remote:debugger:protocol', {
|
||||
command: 'WebAuthn.enable',
|
||||
})
|
||||
const authenticator = await Cypress.automation('remote:debugger:protocol', {
|
||||
command: 'WebAuthn.addVirtualAuthenticator',
|
||||
params: {
|
||||
options: {
|
||||
protocol: 'ctap2',
|
||||
ctap2Version: 'ctap2_1',
|
||||
hasUserVerification: true,
|
||||
transport: 'usb',
|
||||
automaticPresenceSimulation: true,
|
||||
isUserVerified: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
return authenticator
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a virtual authenticator using chrome devbug protocol
|
||||
*
|
||||
* @param authenticator the authenticator object
|
||||
*/
|
||||
async function deleteAuthenticator(authenticator: IChromeVirtualAuthenticator) {
|
||||
await Cypress.automation('remote:debugger:protocol', {
|
||||
command: 'WebAuthn.removeVirtualAuthenticator',
|
||||
params: {
|
||||
...authenticator,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('Login using WebAuthn', () => {
|
||||
let authenticator: IChromeVirtualAuthenticator
|
||||
let user: User
|
||||
|
||||
afterEach(() => {
|
||||
cy.deleteUser(user)
|
||||
.then(() => deleteAuthenticator(authenticator))
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
cy.createRandomUser()
|
||||
.then(($user) => {
|
||||
user = $user
|
||||
cy.login(user)
|
||||
})
|
||||
.then(() => createAuthenticator())
|
||||
.then(($authenticator) => {
|
||||
authenticator = $authenticator
|
||||
cy.log('Created virtual authenticator')
|
||||
})
|
||||
})
|
||||
|
||||
it('add and delete WebAuthn', () => {
|
||||
cy.intercept('**/settings/api/personal/webauthn/registration').as('webauthn')
|
||||
cy.visit('/settings/user/security')
|
||||
|
||||
cy.contains('[role="note"]', /No devices configured/i).should('be.visible')
|
||||
|
||||
cy.findByRole('button', { name: /Add WebAuthn device/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.wait('@webauthn')
|
||||
|
||||
cy.findByRole('textbox', { name: /Device name/i })
|
||||
.should('be.visible')
|
||||
.type('test device{enter}')
|
||||
|
||||
cy.wait('@webauthn')
|
||||
|
||||
cy.contains('[role="note"]', /No devices configured/i).should('not.exist')
|
||||
|
||||
cy.findByRole('list', { name: /following devices are configured for your account/i })
|
||||
.should('be.visible')
|
||||
.contains('li', 'test device')
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: /Actions/i })
|
||||
.click()
|
||||
|
||||
cy.findByRole('menuitem', { name: /Delete/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.contains('[role="note"]', /No devices configured/i).should('be.visible')
|
||||
cy.findByRole('list', { name: /following devices are configured for your account/i })
|
||||
.should('not.exist')
|
||||
|
||||
cy.reload()
|
||||
cy.contains('[role="note"]', /No devices configured/i).should('be.visible')
|
||||
})
|
||||
|
||||
it('add WebAuthn and login', () => {
|
||||
cy.intercept('GET', '**/settings/api/personal/webauthn/registration').as('webauthnSetupInit')
|
||||
cy.intercept('POST', '**/settings/api/personal/webauthn/registration').as('webauthnSetupDone')
|
||||
cy.intercept('POST', '**/login/webauthn/start').as('webauthnLogin')
|
||||
|
||||
cy.visit('/settings/user/security')
|
||||
|
||||
cy.findByRole('button', { name: /Add WebAuthn device/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.wait('@webauthnSetupInit')
|
||||
|
||||
cy.findByRole('textbox', { name: /Device name/i })
|
||||
.should('be.visible')
|
||||
.type('test device{enter}')
|
||||
cy.wait('@webauthnSetupDone')
|
||||
|
||||
cy.findByRole('list', { name: /following devices are configured for your account/i })
|
||||
.should('be.visible')
|
||||
.findByText('test device')
|
||||
.should('be.visible')
|
||||
|
||||
cy.logout()
|
||||
cy.visit('/login')
|
||||
|
||||
cy.findByRole('button', { name: /Log in with a device/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
cy.findByRole('form', { name: /Log in with a device/i })
|
||||
.should('be.visible')
|
||||
.findByRole('textbox', { name: /Login or email/i })
|
||||
.should('be.visible')
|
||||
.type(`{selectAll}${user.userId}`)
|
||||
|
||||
cy.findByRole('button', { name: /Log in/i })
|
||||
.click()
|
||||
cy.wait('@webauthnLogin')
|
||||
|
||||
// Then I see that the current page is the Files app
|
||||
cy.url().should('match', /apps\/dashboard(\/|$)/)
|
||||
})
|
||||
})
|
Loading…
Add table
Reference in a new issue