我正在探索赛普拉斯的e2e测试,看起来很棒的软件。 问题是身份验证,赛普拉斯文档解释了为什么使用UI非常糟糕here。
所以我尝试查看我的应用程序的网络点击,看看我是否可以创建对firebase API的POST请求,并在不使用GUI的情况下进行身份验证。但我可以看到至少有2个请求被触发,并且令牌已保存到应用程序存储中。
那么我应该采用什么方法?
我在这里寻找一些建议:)
答案 0 :(得分:2)
我采用了使用自动UI的方法来获取Firebase JS SDK使用的localStorage的内容。我也想在整个赛普拉斯运行中只执行一次,所以我在赛普拉斯开始之前就已经这样做了。
获取localStorage内容的帮助程序脚本:
const puppeteer = require('puppeteer')
const invokeLogin = async page => {
await page.goto('http://localhost:3000/login')
await page.waitForSelector('.btn-googleplus')
await page.evaluate(() =>
document.querySelector('.btn-googleplus').click())
}
const doLogin = async (page, {username, password}) => {
// Username step
await page.waitForSelector('#identifierId')
await page.evaluate((username) => {
document.querySelector('#identifierId').value = username
document.querySelector('#identifierNext').click()
}, username)
// Password step
await page.waitForSelector('#passwordNext')
await page.evaluate(password =>
setTimeout(() => {
document.querySelector('input[type=password]').value = password
document.querySelector('#passwordNext').click()
}, 3000) // Wait 3 second to next phase to init (couldn't find better way)
, password)
}
const extractStorageEntry = async page =>
page.evaluate(() => {
for (let key in localStorage) {
if (key.startsWith('firebase'))
return {key, value: localStorage[key]}
}
})
const waitForApp = async page => {
await page.waitForSelector('#app')
}
const main = async (credentials, cfg) => {
const browser = await puppeteer.launch(cfg)
const page = await browser.newPage()
await invokeLogin(page)
await doLogin(page, credentials)
await waitForApp(page)
const entry = await extractStorageEntry(page)
console.log(JSON.stringify(entry))
await browser.close()
}
const username = process.argv[2]
const password = process.argv[3]
main({username, password}, {
headless: true // Set to false for debugging
})
由于将JSON作为环境变量发送到赛普拉斯有问题,我使用tmp文件在脚本和赛普拉斯进程之间传递数据。
node test/getFbAuthEntry ${USER} ${PASSWORD} > test/tmp/fbAuth.json
cypress open --env FB_AUTH_FILE=test/tmp/fbAuth.json
在赛普拉斯,我从文件系统中读取它并将其设置为localStorage
const setFbAuth = () =>
cy.readFile(Cypress.env('FB_AUTH_FILE'))
.then(fbAuth => {
const {key, value} = fbAuth
localStorage[key] = value
})
describe('an app something', () => {
it('does stuff', () => {
setFbAuth()
cy.viewport(1300, 800)
...
答案 1 :(得分:2)
这肯定是一个黑客,但为了解决我正在处理的应用程序的登录部分,我使用beforeEach
挂钩登录到应用程序。
beforeEach(() => {
cy.resetTestDatabase().then(() => {
cy.setupTestDatabase();
});
});
这是从我的帮助函数派生的。
Cypress.Commands.add('login', () => {
return firebase
.auth()
.signInWithEmailAndPassword(Cypress.env('USER_EMAIL'), Cypress.env('USER_PASSWORD'));
});
Cypress.Commands.add('resetTestDatabase', () => {
return cy.login().then(() => {
firebase
.database()
.ref(DEFAULT_CATEGORIES_PATH)
.once('value')
.then(snapshot => {
const defaultCategories = snapshot.val();
const updates = {};
updates[TEST_CATEGORIES_PATH] = defaultCategories;
updates[TEST_EVENTS_PATH] = null;
updates[TEST_STATE_PATH] = null;
updates[TEST_EXPORT_PATH] = null;
return firebase
.database()
.ref()
.update(updates);
});
});
});
我想知道的是,从firebase
返回的信息最终会如何保存到localStorage
。我真的没有答案,但它有效。此外,该应用使用.signInWithPopup(new firebase.auth.GoogleAuthProvider())
,而在其上方则使用电子邮件和密码登录。所以我只是因为cypress
具有CORS限制而使签名过程变得简单。
答案 2 :(得分:1)
当我自己做此操作时,我做了一些自定义命令(例如cy.login
进行身份验证,然后cy.callRtdb
和cy.callFirestore
进行数据验证)。在厌倦了重复构建它们所花费的逻辑之后,我将其包装到一个名为cypress-firebase的库中。它包括自定义命令和一个用于生成自定义身份验证令牌的cli。
设置主要只是在cypress/support/commands.js
中添加自定义命令:
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';
import 'firebase/firestore';
import { attachCustomCommands } from 'cypress-firebase';
const fbConfig = {
// Your config from Firebase Console
};
window.fbInstance = firebase.initializeApp(fbConfig);
attachCustomCommands({ Cypress, cy, firebase })
并将插件添加到cypress/plugins/index.js
:
const cypressFirebasePlugin = require('cypress-firebase').plugin
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// Return extended config (with settings from .firebaserc)
return cypressFirebasePlugin(config)
}
但是有关设置are available in the setup docs的完整详细信息。
披露,我是cypress-firebase的作者,这是完整的答案。
答案 3 :(得分:1)
即将推出的Auth emulator变得越来越容易。 Firebase Auth Emulator(firebase-tools
> = 8.1.4)变得更加容易。 >
cypress/support/signAs.js
:
Cypress.Commands.add('signAs', (uid, opt) => {
cy.visit('/')
cy.window().its('firebase').then( fb => {
cy.wrap( (async _ => {
// Create a user based on the provided token (only '.uid' is used by Firebase)
await fb.auth().signInWithCustomToken( JSON.stringify({ uid }) );
// Set '.displayName', '.photoURL'; for email and password, other functions exist (not implemented)
await fb.auth().currentUser.updateProfile(opt);
})() )
})
})
用作:
cy.signAs('joe', { displayName: 'Joe D.', photoURL: 'http://some' });
如果您需要设置.email
或.password
,则可以设置类似的功能,但这对我的测试就足够了。作为测试的一部分,我现在可以临时模拟任何用户。该方法不需要在仿真器中创建用户。您可以使用特定的uid声称自己是一个。对我来说很好。
注意:
Firebase身份验证位于IndexedDB中(如其他答案所述),并且在两次测试之间,赛普拉斯不会清除它。 cypress #1208中对此进行了讨论。
答案 4 :(得分:0)
经过多次试验和错误后,我尝试了解决方案路径2并且运行良好。
所以我的身份验证流程如下:
发送POST请求(使用cybress.request)
https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword
,
并解析响应。创建一个对象:response1 = response.body
发送POST请求(使用cybress.request)
https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo
,
使用prev请求中的idToken。创建一个对象:user = response2.body.users[0];
将对象中的响应与以下属性组合在一起:
const authObject = {
uid: response1.localId,
displayName: response1.displayName,
photoURL: null,
email: response1.email,
phoneNumber: null,
isAnonymous: false,
providerData: [
{
uid: response1.email,
displayName: response1.displayName,
photoURL: null,
email: body.email,
phoneNumber: null,
providerId: 'password'
}
],
'apiKey': apiKey,
'appName': '[DEFAULT]',
'authDomain': '<name of firebase domain>',
'stsTokenManager': {
'apiKey': apiKey,
'refreshToken': response1.refreshToken,
'accessToken': response1.idToken,
'expirationTime': user.lastLoginAt + Number(response1.expiresIn)
},
'redirectEventId': null,
'lastLoginAt': user.lastLoginAt,
'createdAt': user.createdAt
};
然后在cybress中,我只是将这个对象保存在本地storag中,在前钩子中:localStorage.setItem(
firebase:authUser:$ {apiKey}:[DEFAULT] , authObject);
可能不完美,但它解决了这个问题。如果您对代码感兴趣,并且您对如何构建&#34; authObject&#34;或者以其他方式解决此问题有任何了解,请告诉我。
答案 5 :(得分:0)
在撰写本文时,我已经研究了这些方法
最终,我自己实现了它,这很简单。最重要的是,它不需要任何机密的Firebase凭证。基本上是由
完成的const fbConfig = {
apiKey: `your api key`, // AIzaSyDAxS_7M780mI3_tlwnAvpbaqRsQPlmp64
authDomain: `your auth domain`, // onearmy-test-ci.firebaseapp.com
projectId: `your project id`, // onearmy-test-ci
}
firebase.initializeApp(fbConfig)
const attachCustomCommands = (
Cypress,
{ auth, firestore }: typeof firebase,
) => {
let currentUser: null | firebase.User = null
auth().onAuthStateChanged(user => {
currentUser = user
})
Cypress.Commands.add('login', (email, password) => {
Cypress.log({
displayName: 'login',
consoleProps: () => {
return { email, password }
},
})
return auth().signInWithEmailAndPassword(email, password)
})
Cypress.Commands.add('logout', () => {
const userInfo = currentUser ? currentUser.email : 'Not login yet - Skipped'
Cypress.log({
displayName: 'logout',
consoleProps: () => {
return { currentUser: userInfo }
},
})
return auth().signOut()
})
}
attachCustomCommands(Cypress, firebase)
这是具有所有集成代码https://github.com/ONEARMY/community-platform/commit/b441699c856c6aeedb8b73464c05fce542e9ead1
的提交