#!/usr/bin/env python3
"""
Entrypoint
----------
A short script to parse the "score" from the JSON output of Google Lighthouse
and to work as the entry point for a docker container.
"""
# ----------------------------------------------------------------------------
# Imports
# ----------------------------------------------------------------------------
# ---- System
import argparse
import datetime
import json
import os
import subprocess
import sys
# Define version in this file as a centralized and easy place to get to
# and change. Use semanic version as defined at: https://semver.org/
__version__ = "3.0.0"
# ----------------------------------------------------------------------------
# Class: LighthouseTest
# ----------------------------------------------------------------------------
[docs]class LighthouseTest:
"""A lighthouse test as an object."""
def __init__(
self,
url: str,
category: str,
report_type: str = "json",
) -> None:
"""Create a lighthouse test object to manipulate to provide test output."""
self.category = category
self.url = url
self.report_type = report_type
dtg = datetime.datetime.now().strftime("%Y%m%d_%H%M")
self.output_path = (
f"{os.getenv('WORK_DIR')}/lighthouse_{self.category}_{dtg}.{report_type}"
)
[docs] def generate_report(self, report_type) -> str:
"""Use lighthouse to generate an output file."""
chrome_flags = " ".join(
[
"--headless",
"--window-size=821,843",
"--no-sandbox",
"--enable-javascript",
"--ignore-certificate-errors", # ignore https certificate issues
]
)
# All the options for lighthouse can be found at:
# https://github.com/GoogleChrome/lighthouse#cli-options
lighthouse_args = [
self.url,
f'--chrome-flags="{chrome_flags}"',
f'--only-categories "{self.category}"',
"--no-enable-error-reporting",
"--quiet",
f"--output {report_type}",
f"--output-path {self.output_path}",
]
subprocess.run(
" ".join(["lighthouse", *lighthouse_args]),
shell=True,
check=True,
stdout=subprocess.PIPE,
)
return self.output_path
[docs] def parse_score(self):
"""Entry point for the script."""
report_path = self.generate_report(report_type="json")
with open(report_path, "r", encoding="utf-8") as report:
output = json.load(report)
score = int(float(output["categories"][self.category]["score"]) * 100)
# Clean up the file so if there's a volume mount, there's no clutter
if self.report_type == "html":
os.remove(report_path)
return str(score)
# ----------------------------------------------------------------------------
# Functions
# ----------------------------------------------------------------------------
[docs]def parse_args():
"""Return a namespace of the parsed arguments for the program."""
parser = argparse.ArgumentParser(
prog="docker run [various docker options] lighthouse",
description="Run Google Lighthouse inside a docker container with some options.",
)
parser.add_argument(
"--category",
"-c",
type=str,
default="accessibility",
choices=["accessibility", "best-practices", "performance", "pwa", "seo"],
help="The category/type of test to run, defaults to accessibility.",
)
parser.add_argument(
"--url",
"-u",
type=str,
default="https://www.python.org",
help="A valid URL to a web site to test must start with a valid protocol "
"of http:// or https://. Defaults to https://www.python.org",
)
parser.add_argument(
"--report",
"-r",
action="store_true",
help="Generate/workspace the full HTML report to a file. If this flag/option "
"is set only the html file will be generated in the directory mounted "
"with the docker volume flag, i.e. -v /home/user/reports:/workspace "
"as output.html. If the flag is not given a single number, the "
"result of the given test which is between 0 and 100 will be piped "
"to stdout.",
)
parser.add_argument(
"--verbose", "-v", action="store_true", help="Generate more verbose output."
)
all_args = parser.parse_args()
if not (all_args.url.startswith("http://") or all_args.url.startswith("https://")):
print("ERROR: Provide a valid URL, it must start with: http:// or https://")
sys.exit(1)
return all_args
# ----------------------------------------------------------------------------
# Main
# ----------------------------------------------------------------------------
[docs]def main(category: str, url: str, report: bool):
"""Run as the entrypoint and ensure the input is correct."""
# Run Google Lighthouse for the given category and URL
# first set the report type
if report:
report_type = "html"
else:
report_type = "json"
# create the lighthouse test object
lh_test = LighthouseTest(url, category, report_type)
# then run based on report type and output appropriately to stdout
if report:
# print just the file name to stdout,
# i.e. lighthouse_best-practices_20220503_1511.html
print(lh_test.generate_report(report_type).split("/")[-1])
else:
# print just the numeric score to stdout, i.e. 75
print(lh_test.parse_score())
# ----------------------------------------------------------------------------
# Name
# ----------------------------------------------------------------------------
if __name__ == "__main__":
the_args = parse_args()
if the_args.verbose:
print()
print("-" * 40)
print(f"All arguments given: {the_args}")
print()
print(f"Category: {the_args.category}")
print(f"URL: {the_args.url}")
print(f"Report: {the_args.report}")
print(f"Verbose: {the_args.verbose}")
print()
if the_args.report:
print("Output file: ")
else:
print("Score: ")
main(the_args.category, the_args.url, the_args.report)
print("-" * 40)
print()
else:
main(the_args.category, the_args.url, the_args.report)