-
Notifications
You must be signed in to change notification settings - Fork 6.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(screenshot): Improve Firebase Functions for screenshot tests #3732
Conversation
tinayuangao
commented
Mar 23, 2017
- Based on feat(screenshot): Use firebase functions and jwt to make screenshot tests more secure #3628
- Fixes Screenshot Testing - Firebase Functions #3686
- Use typescript. Put functions in seperate files.
- Add function to update Github Status based on result
- Add function to approve a PR (copy test images to goldens)
- Remove functions to upload filenames (We don't need it anymore)
8bcbdd1
to
675197f
Compare
@tinayuangao Should I review this once #3628 is merged? |
675197f
to
16ee200
Compare
@devversion Yes. I will remove |
1f67986
to
dd86b5d
Compare
functions/data_image.ts
Outdated
|
||
/** | ||
* Convert data to images. Image data posted to database will be saved as png files | ||
* and upload to screenshot/$prNumber/dataType/$filename |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uploaded
?
functions/data_image.ts
Outdated
let data = event.data.val(); | ||
let saveFilename = `${event.params.filename}.screenshot.png`; | ||
|
||
if (dataType != 'diff' && dataType != 'test') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: It is always desired to use strict comparison - dataType !== 'diff'
functions/github.ts
Outdated
let result = event.data.val() == true; | ||
let prNumber = event.params.prNumber; | ||
return event.data.ref.parent.child('sha').once('value').then((sha: firebaseAdmin.database.DataSnapshot) => { | ||
return setGithubStatus(sha.val(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the API we used in /tools/gulp/util/github
is a bit more convenient. You just pass in a JSON config instead of X parameters.
functions/package.json
Outdated
"jsonwebtoken": "^7.3.0" | ||
"jsonwebtoken": "^7.3.0", | ||
"request": "^2.81.0", | ||
"typescript": "^2.2.1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we build the functions/*.ts
file before deploying and Firebase shouldn't care about it? If so then we can just remove it from the package file here.
functions/test_goldens.ts
Outdated
let keys: string[] = []; | ||
let counter = 0; | ||
snapshot.forEach((childSnapshot: firebaseAdmin.database.DataSnapshot) => { | ||
if (childSnapshot.val() == false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Strict comparsion
functions/test_goldens.ts
Outdated
keys.push(childSnapshot.key); | ||
} | ||
counter ++; | ||
if (counter == snapshot.numChildren()) return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to manually cancel the enumeration? Doesn't the loop stop automatically when you iterated through all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is.
But typescript requires return boolean, and once boolean returned the forEach
loop will end.
functions/test_goldens.ts
Outdated
export function copyTestImagesToGoldens(prNumber: string) { | ||
return firebaseAdmin.database().ref(`screenshot/reports/${prNumber}/results`).once('value') | ||
.then((snapshot: firebaseAdmin.database.DataSnapshot) => { | ||
let keys: string[] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about failedFilenames
?
functions/tsconfig.json
Outdated
"jwt_util.ts", | ||
"test_goldens.ts", | ||
"util/github.ts", | ||
"util/jwt.ts" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming that index.ts
is your entry-point and all other files are somehow imported by it. You could just add index.ts
as file her.e
functions/util/github.ts
Outdated
description: string, | ||
url: string, | ||
repoSlug: string, | ||
token: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, I would prefer the syntax / API we currently have on tools/gulp/util/github.ts
tools/gulp/tasks/screenshots.ts
Outdated
.then(() => database.goOffline(), () => database.goOffline()); | ||
} | ||
}); | ||
|
||
function updateFileResult(database: firebase.database.Database, prNumber: string, | ||
filenameKey: string, result: boolean) { | ||
return getPullRequestRef(database, prNumber).child('results').child(filenameKey).set(result); | ||
return getPullRequestRef(database, prNumber).child('results').child(filenameKey).set(result) ; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused extra space at line end?
Comments addressed. Please take another look. Thanks! |
functions/data.ts
Outdated
* temporary folder if the token is valid. | ||
* Move the data to 'screenshot/reports/$prNumber/$path | ||
*/ | ||
export function verifyJWTAndUpdateData(event: any, path: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
verifyJwtAndTransferResultToTrustedLocation
?
functions/data.ts
Outdated
@@ -0,0 +1,22 @@ | |||
import * as firebaseAdmin from 'firebase-admin'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could there be a more specific name for this file than data.ts
? Maybe verify-and-copy-screenshot-report.ts
or copy-trusted-screenshot-report.ts
?
In general the screenshot-related files under functions/
should have screenshot
in the name somewhere (since we're likely going to add more firebase functions). A screenshot-test/
subdirectory would also work if firebase supports that.
functions/data.ts
Outdated
* Handle data written to temporary folder. Validate the JWT and move the data out of | ||
* temporary folder if the token is valid. | ||
* Move the data to 'screenshot/reports/$prNumber/$path | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/**
* Verifies that a screenshot report is valid (trusted via JWT) and, if so, copies it from the
* temporary, unauthenticated location to the more permanent, trusted location.
*/
functions/data.ts
Outdated
*/ | ||
export function verifyJWTAndUpdateData(event: any, path: string) { | ||
// Only edit data when it is first created. Exit when the data is deleted. | ||
if (event.data.previous.exists() || !event.data.exists()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to create utility functions for things like this? E.g.,
isEditEvent(event) {
return event.data.previous.exists() && event.data.exists();
}
isDeleteEvent(event) {
return event.data.previous.exists() && !event.data.exists();
}
isCreateEvent(event) {
return !event.data.previous.exists() && event.data.exists();
}
functions/jwt_util.ts
Outdated
* Verify event params have correct JsonWebToken, and execute callback when the JWT is verified. | ||
* Delete the data if there's an error or the callback is done | ||
*/ | ||
export function verifySecureTokenAndExecute(event: firebaseFunctions.Event<any>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this returns a promise, I would just call the function verifySecureToken
. That way, then calling it, it will read like
"Verify secure token, then ..."
functions/data.ts
Outdated
return firebaseAdmin.database().ref().child('screenshot/reports') | ||
.child(prNumber).child(path).set(data); | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How much of the overall screenshot code could be moved to tools/screenshot-testing
and how much needs to live in functions/
? I'd like to try to package everything under tools/
as the shareable package that can be used in other repos. Ideally the bits that live in functions/
would be very minimal.
functions/data_image.ts
Outdated
* Convert data to images. Image data posted to database will be saved as png files | ||
* and uploaded to screenshot/$prNumber/dataType/$filename | ||
*/ | ||
export function convertTestImageDataToFiles(event: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Convert data to images"
What format is the data that's being converted?
functions/data_image.ts
Outdated
let data = event.data.val(); | ||
let saveFilename = `${event.params.filename}.screenshot.png`; | ||
|
||
if (dataType !== 'diff' && dataType !== 'test') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment explaining what this condition is checking for?
functions/index.js
Outdated
const jwt = require('jsonwebtoken'); | ||
const fs = require('fs'); | ||
|
||
Object.defineProperty(exports, "__esModule", { value: true }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's going on with these imports? At the very least it needs an explanatory comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that file was accidentally committed? There is also the index.ts
. @tinayuangao
functions/index.js
Outdated
var image_data_1 = require("./image_data"); | ||
var data_image_1 = require("./data_image"); | ||
var test_goldens_1 = require("./test_goldens"); | ||
var github_1 = require("./github"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer single-quotes over double-quotes.
.gitignore
Outdated
@@ -8,6 +8,8 @@ | |||
|
|||
# dependencies | |||
/node_modules | |||
/functions/node_modules | |||
/tools/screenshot-test/functions/node_modules |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you remove the slash for the /node_modules
above. Then it will include all node_modules/
folders in the whole project.
Just do
node_modules/
fffb5f0
to
62b56a3
Compare
tools/gulp/tasks/screenshots.ts
Outdated
if (prNumber) { | ||
|
||
if (isTravisPushBuild()) { | ||
// Only update goldens for build |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Only update goldens for commits to master
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, it might make sense to rename isTravisPushBuild
to isTravisMasterBuild
(or invert the function and rename it to isTravisPullRequestBuild
)
const bucket = gcs.bucket(firebaseFunctions.config().firebase.storageBucket); | ||
|
||
/** | ||
* Convert data to images. Image data posted to database will be saved as png files |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the incoming data a base-64 encoded image? If so, the comment should say something like
"Writes base-64 encoded test images to png files on the filesystem."
I'd also rename the function to writeTestImagesToFiles
@@ -0,0 +1,39 @@ | |||
import * as firebaseFunctions from 'firebase-functions'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use dashes in the filenames instead of underscores?
I would also call this file write-images.ts
/** | ||
* Read golden files under /goldens/ and store the image data to | ||
* database /screenshot/goldens/$filename | ||
* Convert png image files to BufferArray data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a base-64 encoded string?
* database /screenshot/goldens/$filename | ||
* Convert png image files to BufferArray data | ||
*/ | ||
export function convertGoldenImagesToData(name: string, resourceState: string, fileBucket: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe copyGoldImagesToDatabase
?
} | ||
counter++; | ||
if (counter == snapshot.numChildren()) { | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this true
is used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the type check requires return a boolean.
}); | ||
return failedFilenames; | ||
}).then((failedFilenames: string[]) => { | ||
return bucket.getFiles({prefix: `screenshots/${prNumber}/test`}).then(function (data: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arrow function here?
3d4887a
to
f1edc9e
Compare
8c970fc
to
abe107d
Compare
abe107d
to
c2e8cbc
Compare
LGTM |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |