docs(auth): running Lighthouse on authenticated pages (#9628)
This commit is contained in:
Родитель
364ca59192
Коммит
e7a9b5f304
|
@ -45,3 +45,6 @@ yarn-error.log
|
|||
proto/scripts/*_pb2.*
|
||||
proto/scripts/*_pb.*
|
||||
proto/scripts/*_processed.json
|
||||
|
||||
# require any lock file to be checked in explicitly
|
||||
yarn.lock
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# Running Lighthouse on Authenticated Pages
|
||||
|
||||
Default runs of Lighthouse load a page as a "new user", with no previous session or storage data. This means that pages requiring authenticated access do not work without additional setup. You have a few options for running Lighthouse on pages behind a login:
|
||||
|
||||
## Option 1: Script the login with Puppeteer
|
||||
|
||||
[Puppeteer](https://pptr.dev) is the most flexible approach for running Lighthouse on pages requiring authentication.
|
||||
|
||||
See [a working demo at /docs/recipes/auth](./recipes/auth).
|
||||
|
||||
## Option 2: Leverage logged-in state with Chrome DevTools
|
||||
|
||||
The Audits panel in Chrome DevTools will never clear your cookies, so you can log in to the target site and then run Lighthouse. If `localStorage` or `indexedDB` is important for your authentication purposes, be sure to uncheck `Clear storage`.
|
||||
|
||||
## Option 3: Pass custom request headers with Lighthouse CLI
|
||||
|
||||
CLI:
|
||||
```sh
|
||||
lighthouse http://www.example.com --view --extra-headers="{\"Authorization\":\"...\"}"
|
||||
```
|
||||
|
||||
Node:
|
||||
```js
|
||||
const result = await lighthouse('http://www.example.com', {
|
||||
extraHeaders: {
|
||||
Authorization: '...',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You could also set the `Cookie` header, but beware: it will [override any other Cookies you expect to be there](https://github.com/GoogleChrome/lighthouse/pull/9170). A workaround is to use Puppeteer's [`page.setCookie`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagesetcookiecookies).
|
||||
|
||||
## Option 4: Open a debug instance of Chrome and manually log in
|
||||
|
||||
1. Globally install lighthouse: `npm i -g lighthouse` or `yarn global add lighthouse`. `chrome-debug` is now in your PATH. This binary launches a standalone Chrome instance with an open debugging port.
|
||||
1. Run chrome-debug. This logs the debugging port of your Chrome instance.
|
||||
1. Navigate to your site and log in.
|
||||
1. In a separate terminal, run `lighthouse http://mysite.com --port port-number`, using the port number from chrome-debug.
|
||||
|
||||
## Option 5: Reuse a prepared Chrome User Profile
|
||||
|
||||
This option is currently under development. Track or join the discussion here: [#8957](https://github.com/GoogleChrome/lighthouse/issues/8957).
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* @license Copyright 2019 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
extends: '../../../.eslintrc.js',
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'new-cap': 0,
|
||||
'no-console': 0,
|
||||
'no-unused-vars': 0,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,111 @@
|
|||
# Running Lighthouse on Authenticated Pages with Puppeteer
|
||||
|
||||
If you just want to view the code for using Lighthouse with Puppeteer, see [example-lh-auth.js](./example-lh-auth.js).
|
||||
|
||||
## The Example Site
|
||||
|
||||
There are two pages on the site:
|
||||
|
||||
1. `/` - the homepage
|
||||
2. `/dashboard`
|
||||
|
||||
The homepage shows the login form, but only to users that are not signed in.
|
||||
|
||||
The dashboard shows a secret to users that are logged in, but shows an error to users that are not.
|
||||
|
||||
The server responds with different HTML for each of these pages and session states.
|
||||
|
||||
(Optional) To run the server:
|
||||
```sh
|
||||
# be in root lighthouse directory
|
||||
yarn # install global project deps
|
||||
cd docs/auth
|
||||
yarn # install deps related to just this recipe
|
||||
yarn start # start the server on http://localhost:8000
|
||||
```
|
||||
|
||||
Now that the server is started, let's login with Puppeteer and then run Lighthouse:
|
||||
```sh
|
||||
node example-lh-auth.js
|
||||
```
|
||||
What does this do? Read on....
|
||||
|
||||
## Process
|
||||
|
||||
Puppeteer - a browser automation tool - can be used to programatically setup a session.
|
||||
|
||||
1. Launch a new browser.
|
||||
1. Navigate to the login page.
|
||||
1. Fill and submit the login form.
|
||||
1. Run Lighthouse using the same browser.
|
||||
|
||||
First, launch Chrome:
|
||||
```js
|
||||
// This port will be used by Lighthouse later. The specific port is arbitrary.
|
||||
const PORT = 8041;
|
||||
const browser = await puppeteer.launch({
|
||||
args: [`--remote-debugging-port=${PORT}`],
|
||||
// Optional, if you want to see the tests in action.
|
||||
headless: false,
|
||||
slowMo: 50,
|
||||
});
|
||||
```
|
||||
|
||||
Navigate to the login form:
|
||||
```js
|
||||
const page = await browser.newPage();
|
||||
await page.goto('http://localhost:8000');
|
||||
```
|
||||
|
||||
Given a login form like this:
|
||||
```html
|
||||
<form action="/login" method="post">
|
||||
<label>
|
||||
Email:
|
||||
<input type="email" name="email">
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input type="password" name="password">
|
||||
</label>
|
||||
<input type="submit">
|
||||
</form>
|
||||
```
|
||||
|
||||
Direct Puppeteer to fill and submit it:
|
||||
```js
|
||||
const emailInput = await page.$('input[type="email"]');
|
||||
await emailInput.type('admin@example.com');
|
||||
const passwordInput = await page.$('input[type="password"]');
|
||||
await passwordInput.type('password');
|
||||
await Promise.all([
|
||||
page.$eval('.login-form', form => form.submit()),
|
||||
page.waitForNavigation(),
|
||||
]);
|
||||
```
|
||||
|
||||
At this point, the session that Puppeteer is managing is now logged in.
|
||||
|
||||
Close the page used to log in:
|
||||
```js
|
||||
await page.close();
|
||||
// The page has been closed, but the browser still has the relevant session.
|
||||
```
|
||||
|
||||
Now run Lighthouse, using the same port as before:
|
||||
```js
|
||||
// The local server is running on port 8000.
|
||||
const url = 'http://localhost:8000/dashboard';
|
||||
// Direct Lighthouse to use the same port.
|
||||
const result = await lighthouse(url, { port: PORT });
|
||||
const lhr = result.lhr;
|
||||
|
||||
// Direct Puppeteer to close the browser - we're done with it.
|
||||
await browser.close();
|
||||
```
|
||||
|
||||
All of the above is done in the example script. To run:
|
||||
```sh
|
||||
# make sure server is running (see beginning of recipe) ...
|
||||
node example-lh-auth.js # login via puppeteer and run lighthouse
|
||||
```
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @license Copyright 2019 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @fileoverview Example script for running Lighthouse on an authenticated page.
|
||||
* See docs/recipes/auth/README.md for more.
|
||||
*/
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
const lighthouse = require('lighthouse');
|
||||
|
||||
// This port will be used by Lighthouse later. The specific port is arbitrary.
|
||||
const PORT = 8041;
|
||||
|
||||
/**
|
||||
* @param {import('puppeteer').Browser} browser
|
||||
*/
|
||||
async function login(browser) {
|
||||
const page = await browser.newPage();
|
||||
await page.goto('http://localhost:8000');
|
||||
await page.waitForSelector('input[type="email"]', {visible: true});
|
||||
|
||||
// Fill in and submit login form.
|
||||
const emailInput = await page.$('input[type="email"]');
|
||||
await emailInput.type('admin@example.com');
|
||||
const passwordInput = await page.$('input[type="password"]');
|
||||
await passwordInput.type('password');
|
||||
await Promise.all([
|
||||
page.$eval('.login-form', form => form.submit()),
|
||||
page.waitForNavigation(),
|
||||
]);
|
||||
|
||||
await page.close();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Direct Puppeteer to open Chrome with a specific debugging port.
|
||||
const browser = await puppeteer.launch({
|
||||
args: [`--remote-debugging-port=${PORT}`],
|
||||
// Optional, if you want to see the tests in action.
|
||||
headless: false,
|
||||
slowMo: 50,
|
||||
});
|
||||
|
||||
// Setup the browser session to be logged into our site.
|
||||
await login(browser);
|
||||
|
||||
// The local server is running on port 8000.
|
||||
const url = 'http://localhost:8000/dashboard';
|
||||
// Direct Lighthouse to use the same port.
|
||||
const result = await lighthouse(url, {port: PORT});
|
||||
// Direct Puppeteer to close the browser as we're done with it.
|
||||
await browser.close();
|
||||
|
||||
// Output the result.
|
||||
console.log(JSON.stringify(result.lhr, null, 2));
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"scripts": {
|
||||
"start": "node server/server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.16.2",
|
||||
"lighthouse": "file:../../..",
|
||||
"morgan": "^1.9.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<!--
|
||||
Copyright 2019 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard - Unauthenticated</title>
|
||||
</head>
|
||||
<body>
|
||||
you need to be logged in.
|
||||
<style>
|
||||
body {
|
||||
background-color: #CD5C5C;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,25 @@
|
|||
<!--
|
||||
Copyright 2019 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Dashboard</h1>
|
||||
<div>
|
||||
secrets here
|
||||
</div>
|
||||
<a href="/logout">logout</a>
|
||||
<style>
|
||||
body {
|
||||
background-color: green;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,27 @@
|
|||
<!--
|
||||
Copyright 2019 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Home - Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Plz login</h1>
|
||||
<form class="login-form" action="/login" method="post">
|
||||
<label>
|
||||
Email:
|
||||
<input type="email" name="email">
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input type="password" name="password">
|
||||
</label>
|
||||
<input type="submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,17 @@
|
|||
<!--
|
||||
Copyright 2019 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Home - Logged In</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome home!</h1>
|
||||
<span>You are logged in. Go to <a href="/dashboard">the dashboard</a>.</span>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* @license Copyright 2019 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @fileoverview Example Express server for demonstrating how to run Lighthouse on an authenticated
|
||||
* page. See docs/recipes/auth/README.md for more.
|
||||
*/
|
||||
|
||||
const createError = require('http-errors');
|
||||
const express = require('express');
|
||||
const morgan = require('morgan');
|
||||
const session = require('express-session');
|
||||
const http = require('http');
|
||||
const path = require('path');
|
||||
const PUBLIC_DIR = path.join(__dirname, 'public');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(morgan('dev'));
|
||||
app.use(express.urlencoded({extended: false}));
|
||||
|
||||
app.use(session({
|
||||
secret: 'notverysecret',
|
||||
resave: true,
|
||||
saveUninitialized: false,
|
||||
}));
|
||||
|
||||
app.get('/dashboard', (req, res) => {
|
||||
if (req.session.user) {
|
||||
res.sendFile('./dashboard.html', {root: PUBLIC_DIR});
|
||||
} else {
|
||||
res.status(401).sendFile('./dashboard-unauthenticated.html', {root: PUBLIC_DIR});
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
if (req.session.user) {
|
||||
res.sendFile('home.html', {root: PUBLIC_DIR});
|
||||
} else {
|
||||
res.sendFile('home-unauthenticated.html', {root: PUBLIC_DIR});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/login', (req, res, next) => {
|
||||
const {email, password} = req.body;
|
||||
// super secret login and password ;)
|
||||
if (email !== 'admin@example.com' || password !== 'password') {
|
||||
return next(createError(401));
|
||||
}
|
||||
|
||||
req.session.user = {
|
||||
email,
|
||||
};
|
||||
res.redirect('/dashboard');
|
||||
});
|
||||
|
||||
app.get('/logout', (req, res, next) => {
|
||||
req.session.destroy(() => {
|
||||
res.redirect('/');
|
||||
});
|
||||
});
|
||||
|
||||
// Error handlers
|
||||
app.use(function(req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
app.use(function(err, req, res, next) {
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = err;
|
||||
|
||||
res.status(err.status || 500);
|
||||
res.json({err});
|
||||
});
|
||||
|
||||
const server = http.createServer(app);
|
||||
if (require.main === module) {
|
||||
server.listen(8000);
|
||||
} else {
|
||||
module.exports = server;
|
||||
}
|
Загрузка…
Ссылка в новой задаче