Fix typescript lint issues

Change-Id: Ib38b29e3f0b94890f66db55b79049554c9bd3bc6
Reviewed-on: https://chromium-review.googlesource.com/c/infra/gerrit-plugins/buildbucket/+/3556504
Reviewed-by: Josip Sokcevic <sokcevic@google.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Aravind Vasudevan <aravindvasudev@google.com>
diff --git a/web/auth.ts b/web/auth.ts
index 9a2b3bb..d01c127 100644
--- a/web/auth.ts
+++ b/web/auth.ts
@@ -19,23 +19,23 @@
 // Gerrit JWT Mendel feature flag
 export const GERRIT_JWT_FLAG = 'UiFeature__gerrit_jwt_token_buildbucket_plugin'; // exported for tests only
 
-declare interface OAuthConfig {
-  auth_url?: string
-  proxy_url?: string
-  client_id?: string
-  email?: string
+export declare interface OAuthConfig {
+  auth_url?: string;
+  proxy_url?: string;
+  client_id?: string;
+  email?: string;
 }
 
-declare interface Token {
-  access_token?: string
-  signed_jwt?: string
-  expires_at: number
-  error?: string
+export declare interface Token {
+  access_token?: string;
+  signed_jwt?: string;
+  expires_at: number | null;
+  error?: string;
 }
 
 export declare interface AuthorizationHeader {
-  'X-Gerrit-Auth'?: string
-  authorization?: string
+  'X-Gerrit-Auth'?: string;
+  authorization?: string;
 }
 
 /**
@@ -92,7 +92,7 @@
 export class Authenticator {
   config: OAuthConfig;
   plugin: PluginApi;
-  private readonly token: {[key: string]: Token};
+  private readonly token: {[key: string]: (Token | null)};
   private currentFetchTokenPromise: Promise<Token> | null;
 
   constructor(config: OAuthConfig, plugin: PluginApi) {
@@ -119,7 +119,7 @@
       console.debug('bb: Resolved _fetchToken()');
     }
 
-    return this.token[changeId];
+    return this.token[changeId]!;
   }
 
   /**
@@ -151,7 +151,7 @@
    *     is not logged in. Rejects on an error or timeout.
    */
   private async fetchTokenUnserialized(changeId: string, timeoutMs: number): Promise<Token> {
-    let token: Token = null;
+    let token: Token | null = null;
 
     // Check whether Gerrit JWT feature is enabled
     const experiments = window.ENABLED_EXPERIMENTS || [];
@@ -199,7 +199,7 @@
    * @param token A token object, see https://goo.gl/HZH4Nq.
    * @return True if valid.
    */
-  private isValidToken(token: Token): boolean {
+  private isValidToken(token: Token | null): boolean {
     if (!token || !(token.access_token || token.signed_jwt)) {
       return false;
     }
@@ -236,7 +236,7 @@
  * @param platformJsElement Script element where platform.js
  *     is loaded.
  */
-async function initAuthenticator(plugin: PluginApi, platformJsElement: HTMLScriptElement): Promise<Authenticator> {
+async function initAuthenticator(plugin: PluginApi, platformJsElement: HTMLScriptElement): Promise<Authenticator | null> {
   // Cannot load OAuth config if the user is not logged in.
   console.debug('bb: Awaiting plugin.restApi().getLoggedIn()');
   const loggedIn = await plugin.restApi().getLoggedIn();
diff --git a/web/buildbucket-client.ts b/web/buildbucket-client.ts
index 4e285c7..cebf179 100644
--- a/web/buildbucket-client.ts
+++ b/web/buildbucket-client.ts
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 import {AuthorizationHeader, getAuthorizationHeader} from './auth';
+import {Build} from './checks-fetcher';
 
 // Extra buildbucket tag to be used for collecting metrics
 // about use of the "retry failed" button.
@@ -21,13 +22,14 @@
 }
 
 export declare interface Builder {
+  id?: {builder?: string};
   project?: string;
   builder?: string;
   bucket?: string;
 }
 
-declare interface GerritChange {
-  host: string;
+export declare interface GerritChange {
+  host?: string;
   change: number;
   project: string;
   patchset: number;
@@ -37,7 +39,7 @@
   requests: BuildRequest[];
 }
 
-export type BuildRequest = ScheduleBuildRequest | CancelBuildRequest | SearchBuildRequest | ListBuildersRequest;
+export type BuildRequest = ScheduleBuildRequest | CancelBuildRequest | SearchBuildsRequest | ListBuildersRequest;
 
 export declare interface ScheduleBuildRequest {
   scheduleBuild: {
@@ -57,22 +59,32 @@
   };
 }
 
-export declare interface SearchBuildRequest {
+export declare interface SearchBuildsRequest {
   searchBuilds: {
     pageSize: number;
     predicate: {
       includeExperimental: boolean;
       gerritChanges: GerritChange[];
+      builder?: Builder;
     };
-    mask: {
+    mask?: {
       fields: string;
       outputProperties: [
         {[key: string]: string[]}
       ];
     };
+    pageToken?: number;
   }
 }
 
+export declare interface SearchBuildsResponse {
+  searchBuilds: {
+    builds: Build[];
+    nextPageToken: number;
+  };
+  error: any;
+}
+
 export declare interface ListBuildersRequest {
   project: string;
   bucket: string;
@@ -80,6 +92,11 @@
   pageSize: number;
 }
 
+export declare interface ListBuildersResponse {
+  builders: Builder[];
+  nextPageToken: string;
+}
+
 /**
  * Constructs a batch request to schedule builds for a set of builders.
  *
@@ -124,7 +141,7 @@
  */
 export async function retry(fn: () => any, timeout: number, maxTimeout: number,
                             maxAttempts: number, exp: number): Promise<any> {
-  const sleep = (ms: number) => {new Promise(resolve => setTimeout(resolve, ms))};
+  const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
   let attempt = 0;
   while (attempt < maxAttempts) {
     try {
@@ -166,7 +183,7 @@
    * @param signal An AbortSignal object.
    * @return Response body.
    */
-  private async call(service: string, method: string, request: BuildRequests | BuildRequest, signal: AbortSignal): Promise<any> {
+  private async call(service: string, method: string, request: BuildRequests | BuildRequest, signal: AbortSignal | undefined): Promise<any> {
     // Miniature implementation of pRPC protocol with JSONPB and auth.
     const authHeader = await this.getAuthorizationHeader();
     const headers = {
@@ -209,7 +226,7 @@
     return this.call('buildbucket.v2.Builds', 'Batch', request, signal);
   }
 
-  searchBuilds(request: SearchBuildRequest, signal: AbortSignal | undefined = undefined): Promise<any> {
+  searchBuilds(request: SearchBuildsRequest, signal: AbortSignal | undefined = undefined): Promise<SearchBuildsResponse> {
     return this.call('buildbucket.v2.Builds', 'SearchBuilds', request, signal);
   }
 
@@ -222,7 +239,7 @@
     return this.call('buildbucket.v2.Builds', 'CancelBuild', request, signal);
   }
 
-  listBuilders(request: ListBuildersRequest, signal: AbortSignal | undefined = undefined): Promise<any> {
+  listBuilders(request: ListBuildersRequest, signal: AbortSignal | undefined = undefined): Promise<ListBuildersResponse> {
     return this.call(
         'buildbucket.v2.Builders', 'ListBuilders', request, signal);
   }
@@ -232,7 +249,7 @@
    *
    * @returns authorization header to use in requests.
    */
-  private getAuthorizationHeader(): Promise<AuthorizationHeader> {
+  getAuthorizationHeader(): Promise<AuthorizationHeader> {
     return getAuthorizationHeader(this.changeId);
   }
 }
diff --git a/web/buildbucket-utils.ts b/web/buildbucket-utils.ts
index 99cbad7..ca03acb 100644
--- a/web/buildbucket-utils.ts
+++ b/web/buildbucket-utils.ts
@@ -87,7 +87,7 @@
  * @param loggedIn Whether the user is logged in.
  * @param change Change object.
  */
-export function showChooseTryjobs(config: Config, loggedIn: boolean, change: ChangeInfo): boolean {
+export function showChooseTryjobs(config: Config | null, loggedIn: boolean, change: ChangeInfo): boolean {
   return !!(config?.buckets?.length && loggedIn && change.status === 'NEW');
 }
 
@@ -100,7 +100,7 @@
  * @param loggedIn Whether the user is logged in.
  * @param change Change object.
  */
-export function showRetryFailed(config: Config, loggedIn: boolean, change: ChangeInfo): boolean {
+export function showRetryFailed(config: Config | null, loggedIn: boolean, change: ChangeInfo): boolean {
   return !!(config && !config.hideRetryButton && loggedIn &&
       change.status === 'NEW');
 }
@@ -150,8 +150,8 @@
  *   and the second with experimental builds.
  */
 export function splitExperimentalBuilds(allBuilds: Build[]): Build[][] {
-  const builds = [];
-  const experimentalBuilds = [];
+  const builds: Build[] = [];
+  const experimentalBuilds: Build[] = [];
   allBuilds.forEach(b => {
     if (isExperimental(b)) {
       experimentalBuilds.push(b);
@@ -182,7 +182,7 @@
 export function getFailedBuilders(allBuilds: Build[]): Builder[] {
   // Only consider non-experimental builds.
   const [builds] = splitExperimentalBuilds(allBuilds);
-  const builders = new Map();
+  const builders: Map<string, Builder> = new Map();
   const seen = new Set();
   builds.sort((a, b) => -compareBuildIds(a.id, b.id));
   builds.forEach(b => {
@@ -254,10 +254,10 @@
     'NO_CODE_CHANGE',
   ]);
   const revisions = reversedRevisions(change);
-  const equivalentSets = [];
-  let currentSet = new Set();
+  const equivalentSets: Set<number>[] = [];
+  let currentSet: Set<number> = new Set();
   for (let i = 0; i < revisions.length; i++) {
-    currentSet.add(revisions[i]._number);
+    currentSet.add(revisions[i]._number as number);
     if (!trivialKinds.has(revisions[i].kind)) {
       // If this revision was a non-trivial change, it is not part
       // of the same equivalent patchset set as those after it.
@@ -278,11 +278,11 @@
  *
  * @param timestamp A timestamp string with the above format.
  */
-export function getDateFromTimestamp(timestamp: string): Date {
-  if (!timestamp) { return null; }
+export function getDateFromTimestamp(timestamp: string | undefined): Date | undefined {
+  if (!timestamp) { return undefined; }
   const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,9})?Z$/
-      .exec(timestamp);
-  if (!m) { return null; }
+      .exec(String(timestamp));
+  if (!m) { return undefined; }
 
   // m[2] matches the month with January being 01. The Date constructor takes a
   // 0-indexed month.
diff --git a/web/checks-fetcher.ts b/web/checks-fetcher.ts
index 3552321..ec9dac8 100644
--- a/web/checks-fetcher.ts
+++ b/web/checks-fetcher.ts
@@ -13,7 +13,8 @@
     RETRY_FAILED_TAG,
     Builder,
     BuildRequests,
-    SearchBuildRequest,
+    SearchBuildsRequest,
+    SearchBuildsResponse,
   } from './buildbucket-client';
 import {
     createBuilderLink,
@@ -32,6 +33,7 @@
     showRetryFailed,
   } from './buildbucket-utils';
 import {
+    Artifact,
     ResultDbV1Client,
     TestStatus,
     TestVariant,
@@ -45,7 +47,17 @@
 } from './resultdb-utils';
 import {openTryjobPicker} from './cr-tryjob-picker';
 import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
-import {Category, ChangeData, CheckResult, Link, LinkIcon, ResponseCode, Tag, TagColor} from '@gerritcodereview/typescript-api/checks';
+import {
+  Action,
+  Category,
+  ChangeData,
+  CheckResult,
+  Link,
+  LinkIcon,
+  ResponseCode,
+  Tag,
+  TagColor
+} from '@gerritcodereview/typescript-api/checks';
 import {ChangeInfo} from '@gerritcodereview/typescript-api/rest-api';
 
 // TODO(aravindvasudev): Update to @gerritcodereview/typescript-api/checks implementation once Gerrit updates their API.
@@ -86,22 +98,22 @@
 const ICON_SPACE = '\u2005\u2005';
 
 export declare interface Build {
-  id?: string;
-  builder?: Builder;
+  id: string;
+  builder: Builder;
   number?: number;
   tags?: {
     key: string;
     value: string;
   }[];
-  status?: BuildStatus;
+  status: BuildStatus;
   critical?: string;
-  createTime?: string;
-  startTime?: string;
-  endTime?: string;
+  createTime: string;
+  startTime: string;
+  endTime: string;
   summaryMarkdown?: string;
   infra?: {
     resultdb?: {
-      invocation: boolean;
+      invocation: string;
       hostname: string;
     }
   };
@@ -131,24 +143,28 @@
 }
 
 // TODO(aravindvasudev): Update to @gerritcodereview/typescript-api/checks implementation once Gerrit updates their API.
-declare interface CheckRun {
-  patchset: number;
-  attempt: number;
-  externalId: string;
+export declare interface CheckRun {
+  change?: number;
+  patchset?: number;
+  attempt?: number;
+  externalId?: string;
   checkName: string;
-  checkLink: string;
+  checkDescription?: string;
+  checkLink?: string;
   status: RunStatus;
-  statusDescription: string;
-  statusLink: string;
-  scheduledTimestamp: Date;
-  startedTimestamp: Date;
-  finishedTimestamp: Date;
-  results: any[];
+  statusDescription?: string;
+  statusLink?: string;
+  labelName?: string;
+  actions?: Action[];
+  scheduledTimestamp?: Date;
+  startedTimestamp?: Date;
+  finishedTimestamp?: Date;
+  results?: CheckResult[];
 }
 
 export class ChecksFetcher {
   plugin: PluginApi;
-  config: Config;
+  config: Config | null;
   buildbucketHost: string;
   maxVariantsPerBuild: number;
   includeAdditionalResults: boolean;
@@ -175,7 +191,7 @@
     // first fetch, don't abort. Otherwise, abort using old controller.
     const controllerKey = this.getChangeDataId(changeData);
     if (this.controllers.has(controllerKey)) {
-      this.controllers.get(controllerKey).abort();
+      this.controllers.get(controllerKey)?.abort();
     }
     const controller = new AbortController();
     this.controllers.set(controllerKey, controller);
@@ -185,7 +201,7 @@
     if (!this.config) {
       const pluginName = encodeURIComponent(this.plugin.getPluginName());
       const config = await this.plugin.restApi()
-          .get(`/projects/${encodeURIComponent(repo)}/${pluginName}~config`);
+          .get(`/projects/${encodeURIComponent(repo)}/${pluginName}~config`) as Config;
       if (!config) {
         console.info('Buildbucket plugin not configured for this project.');
         return {responseCode: ResponseCode.OK};
@@ -211,7 +227,7 @@
     let complete = true;
     const runs = [];
     const allAttempts = this.createAttemptList(builds);
-    const latestPatchset = Object.keys(changeInfo.revisions).length;
+    const latestPatchset = Object.keys(changeInfo.revisions as {}).length;
 
     for (const [i, build] of builds.entries()) {
       const run = this.convertBuildToRun(
@@ -219,7 +235,7 @@
           build,
           patchsetNumber,
           latestPatchset,
-          allAttempts[build.builder.builder],
+          allAttempts[build.builder.builder!],
           repo);
       if (!run) {
         continue;
@@ -232,6 +248,11 @@
       }
       const checkResults = await this.convertVariantsToCheckResults(
           build, (result as PromiseFulfilledResult<TestVariant[]>).value, changeNumber);
+
+      if (!run.results) {
+        run.results = [];
+      }
+
       run.results.push(...checkResults);
       runs.push(run);
     };
@@ -270,7 +291,7 @@
         primary: true,
         summary: true,
         tooltip: 'Select specific builders to run on the latest patchset',
-        callback: (change) =>
+        callback: (change: number) =>
             this.chooseTryjobsCallback(change, latestPatchset, repo),
       });
     }
@@ -284,7 +305,7 @@
         primary: true,
         summary: false,
         tooltip: 'Retry any failed builds from all patchsets',
-        callback: (change) =>
+        callback: (change: number) =>
             this.retryFailedBuildsCallback(
                 change, latestPatchset, repo, controller),
       });
@@ -342,7 +363,7 @@
    * Compared to fetchBuilds, this method handles fetching equivalent patchsets.
    */
   async fetchDisplayedBuilds(change: number, patchset: number, project: string, changeInfo: ChangeInfo, controller: AbortController): Promise<Build[]> {
-    let patchsets = [];
+    let patchsets: number[] = [];
     const groups = splitPatchsetGroups(changeInfo);
 
     for (const group of groups) {
@@ -403,7 +424,7 @@
       'infra.resultdb',
     ].join(',');
 
-    let requests: SearchBuildRequest[] = patchsets.map(patchset => ({
+    let requests: SearchBuildsRequest[] = patchsets.map(patchset => ({
       searchBuilds: {
         pageSize: 1000,
         predicate: {
@@ -429,18 +450,18 @@
       const {responses} = await retry(
           batch, DEFAULT_UPDATE_INTERVAL_MS, MAX_UPDATE_INTERVAL_MS, 3, 2);
 
-      const newRequests = [];
-      (responses || []).forEach(({searchBuilds, error}, i) => {
-        if (error) {
+      const newRequests: SearchBuildsRequest[] = [];
+      (responses || []).forEach((response: SearchBuildsResponse, i: number) => {
+        if (response.error) {
           console.error('Buildbucket request failed with error code ' +
-              `${error.code}: ${error.message}`);
+              `${response.error.code}: ${response.error.message}`);
           return;
         }
-        builds.push(...(searchBuilds?.builds || []));
+        builds.push(...(response.searchBuilds?.builds || []));
 
         // If there are more builds to fetch, add the request to be re-fetched.
-        if (searchBuilds?.nextPageToken) {
-          requests[i].searchBuilds['pageToken'] = searchBuilds.nextPageToken;
+        if (response.searchBuilds?.nextPageToken) {
+          requests[i].searchBuilds.pageToken = response.searchBuilds.nextPageToken;
           newRequests.push(requests[i]);
         }
       });
@@ -486,7 +507,7 @@
     const buildResourceName =
         createBuildResourceName(this.buildbucketHost, build.id);
     const currAttemptNum = attempts.map(a => a.id).indexOf(build.id) + 1;
-    const run = {
+    const run: CheckRun = {
       patchset: currPatchset,
       attempt: currAttemptNum,
       externalId: buildResourceName,
@@ -530,7 +551,7 @@
 
         // Using the same result category for each previous attempt result means
         // attempts are grouped together.
-        run.results.push({
+        run.results?.push({
           externalId: createBuildResourceName(this.buildbucketHost, currBuild.id),
           category: maxCategory,
           summary: this.getResultSummaryFromBuildAttempt(currBuild, attemptNum),
@@ -544,7 +565,7 @@
       // To minimize clutter for the user, SUCCESSful and STARTED builds should
       // not contain the Buildbucket CheckResult. Relevant information should be
       // put in the CheckRun object.
-      run.results.push({
+      run.results?.push({
         externalId: buildResourceName,
         category: maxCategory,
         summary: this.getResultSummaryFromBuild(build),
@@ -572,7 +593,7 @@
       });
     }
 
-    const checkResults = [];
+    const checkResults: CheckResult[] = [];
     for (const variant of testVariants) {
       const variantTags = [...commonTags];
 
@@ -611,7 +632,7 @@
           this.includeAdditionalResults ? variant.results.length : 1;
       const variantName = createVariantName(variant);
       // TODO(gavinmak): Simplify data.
-      const rdbHost = build.infra.resultdb.hostname;
+      const rdbHost = build.infra?.resultdb?.hostname || '';
       const data = {
         plugin: this.plugin,
         variant: {variant: variant.variant},
@@ -624,9 +645,9 @@
         changeId,
       };
 
-      const messages = [];
-      const allInvArtifacts = [];
-      const resArtifactLinks = [];
+      const messages: string[] = [];
+      const allInvArtifacts: Artifact[] = [];
+      const resArtifactLinks: Link[] = [];
       const variantLinks = this.createTestResultLinks(variant, build);
       const client = new ResultDbV1Client(rdbHost, String(changeId));
       for (let i = 0; i < numTestResults; i++) {
@@ -646,6 +667,7 @@
       }
 
       // Result artifacts are named 'Run #${runNumber}: ${tooltip}'.
+      // @ts-ignore
       resArtifactLinks.sort((a, b) => a.tooltip.localeCompare(b.tooltip));
 
       // Dedupe invocation-level artifacts.
@@ -670,12 +692,12 @@
         ],
         message: messages.join(' '),
         [DATA_SYMBOL]: data,
-      });
+      } as any);
     }
 
     // Sort and group tests by test suite.
-    function sortResults(element) {
-      checkResults.sort((a, b) => {
+    function sortResults(element: string) {
+      checkResults.sort((a: any, b: any) => {
         const aName = a[DATA_SYMBOL][element]?.toUpperCase();
         const bName = b[DATA_SYMBOL][element]?.toUpperCase();
         if (aName < bName) {
@@ -915,12 +937,12 @@
    * TODO(gavinmak): Refactor to only include build and isQuickRun.
    */
   createAttemptList(builds: Build[]): {[key: string]: Attempt[]} {
-    const dct = {};
+    const dct: {[key: string]: Attempt[]} = {};
     builds.forEach(build => {
       const {id, status} = build;
       const isQuickRun = !!build.output?.properties?.rts_was_used;
       const info: Attempt = {id, status, isQuickRun, build};
-      const name = build.builder.builder;
+      const name = build.builder.builder!;
       if (!(name in dct)) {
         dct[name] = [info];
       } else {
@@ -935,8 +957,8 @@
   /**
    * Returns a list of actions used in a Reboot Checks API `Run` object.
    */
-  createRunActions(change: number, build: Build, project: string, patchset: number, attempts: Attempt[]): any[] {
-    const actions = [];
+  createRunActions(change: number, build: Build, project: string, patchset: number, attempts: Attempt[]): Action[] {
+    const actions: Action[] = [];
     if (!this.retryEnabled) {
       return actions;
     }
@@ -998,7 +1020,7 @@
     const {responses} = await retry(
         batch, DEFAULT_UPDATE_INTERVAL_MS, MAX_UPDATE_INTERVAL_MS, 3, 2);
     let numError = 0;
-    (responses || []).forEach(r => {
+    (responses || []).forEach((r: {error: any}) => {
       if (r.error) {
         numError++;
         console.warn('Batch request failed:', r.error);
@@ -1017,7 +1039,7 @@
    * ActionCallback that cancels any existing builds. If a build is in an end
    * state, this is a no-op.
    */
-  async cancelRunsCallback(change: number, builds: Build[]): Promise<{message: string}> {
+  async cancelRunsCallback(change: number, builds: Attempt[]): Promise<{message: string}> {
     const requests = builds.map(build => ({
       cancelBuild: {
         id: build.id,
@@ -1098,7 +1120,7 @@
   /**
    * Cleans up a string to be used for sorting results.
    */
-  sortName(s) {
+  sortName(s: string) {
     return (s || '').replace(/[^\x00-\x7F]+/gm, '').trim().toUpperCase();
   }
 
@@ -1116,7 +1138,7 @@
    *
    * @returns authorization header to use in requests.
    */
-  private getAuthorizationHeader(changeId: number): Promise<AuthorizationHeader> {
+  getAuthorizationHeader(changeId: number): Promise<AuthorizationHeader> {
     return getAuthorizationHeader(String(changeId));
   }
 }
@@ -1129,7 +1151,7 @@
  * @param number The numbered run this belongs to. If this is null or
  *   undefined, the link is not labeled with a run number.
  */
-export function createArtifactLink(artifact: any, isInvLevel: boolean, number: number): Link {
+export function createArtifactLink(artifact: any, isInvLevel: boolean, number: number | null): Link {
   let tooltip = number != null ? `Run ${number}: ` : '';
   tooltip += artifact.artifactId + (isInvLevel ? ' of Parent Invocation' : '');
   return {
@@ -1140,7 +1162,7 @@
   };
 }
 
-export function createTestResultSummary(result: any): string {
+export function createTestResultSummary(result: any): string | null {
   const {status, expected} = result;
   const prefix = expected ? 'expectedly ' : 'unexpectedly ';
   if (status === TestStatus.PASS) {
diff --git a/web/checks-result.ts b/web/checks-result.ts
index 7a8f3ec..84d6241 100644
--- a/web/checks-result.ts
+++ b/web/checks-result.ts
@@ -10,6 +10,7 @@
     createArtifactLink,
     createTestResultSummary,
   } from './checks-fetcher';
+import {Link} from '@gerritcodereview/typescript-api/checks';
 
 // The maximum number of bytes to fetch when fetching text artifacts.
 const MAX_ARTIFACT_SIZE = 50000; // 50 KB
@@ -92,7 +93,7 @@
  *
  * @param element: the check-result-expanded element.
  */
-export async function installChecksResult(element) {
+export async function installChecksResult(element: any) {
   if (!element?.result?.message) return;
 
   // Remove message div with raw message string.
@@ -117,9 +118,9 @@
     EXPANDED_TEST_ROW_STYLE);
   const {run: checkRun, result: checkResult} = element;
 
-  const allInvArtifacts = [];
-  const resArtifactLinks = [];
-  const htmlPromises = data.testResults.map(async (r, i) => {
+  const allInvArtifacts: Artifact[] = [];
+  const resArtifactLinks: Link[] = [];
+  const htmlPromises = data.testResults.map(async (r: any, i: number) => {
     const testResult = r.result;
     const {summaryHtml, name} = testResult;
     // Fetch artifacts.
@@ -156,6 +157,7 @@
   // Update CheckResult links if not already added.
   if (!data.addedArtifactLinks) {
     // Result artifacts are named 'Run #${runNumber}: ${tooltip}'.
+    // @ts-ignore
     resArtifactLinks.sort((a, b) => a.tooltip.localeCompare(b.tooltip));
 
     // Dedupe invocation-level artifacts.
@@ -185,7 +187,7 @@
   let exonerations = '';
   if (data.testExonerations?.length > 0) {
     exonerations = '<br /><br />' +
-        data.testExonerations.map(e => e.explanationHtml).join('<br />');
+        data.testExonerations.map((e: any) => e.explanationHtml).join('<br />');
   }
 
   // Use <ul> instead of <ol> since results are labeled 'Run #x'.
@@ -224,8 +226,8 @@
   await Promise.allSettled([...textArtifacts].map(async element => {
     const artifactMap =
         element.hasAttribute('inv-level') ? invArtifactMap : resArtifactMap;
-    const id = element.getAttribute('artifact-id');
-    const {fetchUrl, name, sizeBytes} = artifactMap.get(id);
+    const id = element.getAttribute('artifact-id') as string;
+    const {fetchUrl, name, sizeBytes} = artifactMap.get(id)!;
     if (!fetchUrl) { return; }
     if (!sizeBytes) {
       element.replaceWith(metadataElement(`${id} artifact is empty`));
@@ -268,7 +270,7 @@
       // HTML.
       const nodes = await nodePromise;
       // Replacing without copying the nodes may lead to empty artifacts.
-      const copy = nodes.map(n => n.cloneNode(true));
+      const copy = nodes.map((n: Node) => n.cloneNode(true));
       element.replaceWith(...copy);
     } catch (e: unknown) {
       // Evict the rejected promise from the cache.
@@ -323,19 +325,19 @@
  * @param md: the markdown to parse.
  */
 export function parseMarkdown(md: string) {
-  function process(md) {
+  function process(md: string) {
     // Create links.
     md = md.replaceAll(
         /\[((?!\]\()[\s\S]*?)?\]\(([^\)]*)\)/gm,
-        (_m, p1, p2) => `<a href='${p2}'>${p1 || ''}</a>`);
+        (_m: any, p1: string, p2: string) => `<a href='${p2}'>${p1 || ''}</a>`);
 
     // Replace ** and __ strings.
     md = md.replaceAll(
         /(\*\*|__)(([\S]((?!\1)[ \t\S])*?)?[\S])\1/gm,
-        (_m, _p1, p2) => `<strong>${p2}</strong>`);
+        (_m: any, _p1: string, p2: string) => `<strong>${p2}</strong>`);
 
     // Replace # with <h1> and so on up to <h6>.
-    md = md.replaceAll(/^(#+) (.*)/gm, (m, p1, p2) => {
+    md = md.replaceAll(/^(#+) (.*)/gm, (m: any, p1: string, p2: string) => {
       if (p1.length > 6) {
         return m;
       }
diff --git a/web/cr-tryjob-picker.ts b/web/cr-tryjob-picker.ts
index 2c3ac84..c067616 100644
--- a/web/cr-tryjob-picker.ts
+++ b/web/cr-tryjob-picker.ts
@@ -5,11 +5,17 @@
 import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
 import {css, html, LitElement} from 'lit';
 import {customElement, property, state} from 'lit/decorators';
-import {BuildbucketV2Client, Builder, makeBuildRequests} from './buildbucket-client';
+import {BuildbucketV2Client, Builder, GerritChange, makeBuildRequests} from './buildbucket-client';
 import {getNewOperationId} from './buildbucket-utils';
 import {Config} from './checks-fetcher';
 
-let _TRYJOB_PICKER = null;
+let _TRYJOB_PICKER: any = null;
+
+declare interface Bucket {
+  project: string;
+  bucket: string;
+  builders: string[];
+}
 
 /**
  * Open a CrTryjobPicker popup with the given configuration and change info.
@@ -21,7 +27,7 @@
  * @param change: the change number
  * @param patchset: the patchset number
  */
-export async function openTryjobPicker(plugin: PluginApi, pluginConfig: Config, buildbucketHost: string,
+export async function openTryjobPicker(plugin: PluginApi, pluginConfig: Config | null, buildbucketHost: string,
     project: string, change: number, patchset: number) {
   if (_TRYJOB_PICKER) {
     _TRYJOB_PICKER.close();
@@ -47,16 +53,16 @@
 @customElement('cr-tryjob-picker')
 export class CrTryjobPicker extends LitElement {
   @property()
-  buildbucketHost: string;
+  buildbucketHost: string | undefined;
 
   @property()
   change: {_number?: number, project?: string} = {};
 
   @property()
-  revision: {_number?: number};
+  revision: { _number?: number; } | undefined;
 
   @property()
-  plugin: PluginApi;
+  plugin: PluginApi | undefined;
 
   @property()
   disabled: boolean = false;
@@ -65,16 +71,16 @@
   bucketListMessage: string = '';
 
   @state()
-  buckets: any[] = [];
+  buckets: Bucket[] = [];
 
   @state()
-  clientOperationId: string;
+  clientOperationId: string | undefined;
 
   @state()
-  filterClass: string;
+  filterClass: string | undefined;
 
   @state()
-  matchesFilter: ((string) => boolean) = _ => true;
+  matchesFilter: ((arg0: string) => boolean) = _ => true;
 
   @state()
   selectedBuilders: {[key: string]: Builder} = {};
@@ -207,7 +213,7 @@
         <input autofocus
             class="filter ${this.filterClass}"
             placeholder="Regex filter"
-            @input=${e => this.filter = e.target.value}>
+            @input=${(e: Event) => this.filter = (e.target as HTMLInputElement)?.value}>
         <div class="list">
           <div>${this.bucketListMessage}</div>
           ${bucketsHTML}
@@ -256,7 +262,7 @@
       if (!response.builders || response.builders.length === 0) {
         break;
       }
-      builders.push(...response.builders.map(b => b.id.builder));
+      builders.push(...response.builders.map(b => b.id?.builder ?? ''));
       pageToken = response.nextPageToken;
       if (!pageToken) {
         break;
@@ -285,8 +291,8 @@
     const client = new BuildbucketV2Client(
       this.buildbucketHost, String(this.change._number));
 
-    const buckets = new Map();
-    const promises = [];
+    const buckets: Map<string, {project: string; bucket: string; builders: string[]}> = new Map();
+    const promises: Promise<any>[] = [];
 
     cfgBuckets.forEach(b => {
       const luciPrefixLength = 'luci.'.length;
@@ -306,7 +312,9 @@
       }
       const {project, bucket, builders} = result.value;
       const b = buckets.get(`${project}/${bucket}`);
-      b.builders = Array.from(new Set(b.builders.concat(builders))).sort();
+      if (b) {
+        b.builders = Array.from(new Set(b.builders.concat(builders))).sort();
+      }
     });
     return Array.from(buckets.values());
   }
@@ -363,8 +371,8 @@
     }
   }
 
-  computeBucketFilterFn(builderPredicate: (string) => boolean) {
-    return bucket => bucket.builders.some(builderPredicate);
+  computeBucketFilterFn(builderPredicate: (arg0: string) => boolean) {
+    return (bucket: Bucket) => bucket.builders.some(builderPredicate);
   }
 
   private computeAddButtonDisabled(selectedBuilders: {[key: string]: Builder}, disabled: boolean) {
@@ -397,7 +405,7 @@
       if (!bucketToBuilders[bucket]) {
         bucketToBuilders[bucket] = [];
       }
-      bucketToBuilders[bucket].push(b.builder);
+      bucketToBuilders[bucket].push(b.builder!);
     });
     const includeTrybotStrings = [];
     for (let [bucket, builders] of Object.entries(bucketToBuilders)) {
@@ -416,7 +424,7 @@
     const project = el.getAttribute('data-project');
     const bucket = el.getAttribute('data-bucket');
     const builder = el.value;
-    const builderKey = this.builderKey(project, bucket, builder);
+    const builderKey = this.builderKey(project!, bucket!, builder);
     if (el.checked) {
       this.selectedBuilders = Object.assign(
           this.selectedBuilders, {[builderKey]: {project, bucket, builder}});
@@ -434,11 +442,11 @@
   private async scheduleSelectedBuilders(): Promise<void> {
     this.clientOperationId = this.clientOperationId || getNewOperationId();
     const builders = Object.values(this.selectedBuilders);
-    const gerritChanges = [{
+    const gerritChanges: GerritChange[] = [{
       host: this.pluginConfig.gerritHost,
-      change: this.change._number,
-      project: this.change.project,
-      patchset: this.revision._number,
+      change: this.change._number!,
+      project: this.change.project!,
+      patchset: this.revision!._number!,
     }];
     const requests = makeBuildRequests(
       builders,
@@ -446,9 +454,9 @@
       this.clientOperationId);
 
     const client = new BuildbucketV2Client(
-      this.buildbucketHost, String(this.change._number));
+      this.buildbucketHost!, String(this.change._number));
     const {responses} = await client.batch(requests);
-    (responses || []).forEach(r => {
+    (responses || []).forEach((r: any) => {
       if (r.error) {
         throw new Error(r.error.message);
       }
@@ -498,7 +506,7 @@
 
   /** Resets the state of the picker element. */
   private reset(): void {
-    this.clientOperationId = null;
+    this.clientOperationId = undefined;
     this.filter = '';
     this.selectedBuilders = {};
     this.uncheckAllBoxes();
@@ -511,8 +519,7 @@
     // change event, which means that binding the value to selectedBuilders
     // won't actually cause the checkbox to be unchecked. This must be done
     // manually.
-    const checkboxes = this.shadowRoot
-        .querySelectorAll('input[type="checkbox"]') as unknown as Element[];
+    const checkboxes = this.shadowRoot?.querySelectorAll('input[type="checkbox"]') as unknown as Element[];
     for (const c of checkboxes) {
       (c as HTMLInputElement).checked = false;
     }
diff --git a/web/plugin.ts b/web/plugin.ts
index d601a5c..559693e 100644
--- a/web/plugin.ts
+++ b/web/plugin.ts
@@ -22,8 +22,9 @@
 
   const buildbucketHost = 'cr-buildbucket.appspot.com';
   const fetcher = new ChecksFetcher(plugin, buildbucketHost, 100);
+  // TODO(aravindvasudev): Update to @gerritcodereview/typescript-api/checks implementation once Gerrit updates their API.
   plugin.checks().register(
-    {fetch: (changeData) => fetcher.fetch(changeData)},
+    {fetch: (changeData) => (fetcher.fetch(changeData) as any)},
     {fetchPollingIntervalSeconds: 10},
   );
   plugin.hook('check-result-expanded').onAttached(installChecksResult);
diff --git a/web/resultdb-client.ts b/web/resultdb-client.ts
index 8ccb012..7ae5e20 100644
--- a/web/resultdb-client.ts
+++ b/web/resultdb-client.ts
@@ -44,13 +44,16 @@
   testId?: string;
   resultId?: string;
   tags: {[key: string]: string}[];
+  status?: TestStatus;
+  summaryHtml?: string;
+  expected?: boolean;
 }
 
 export declare interface Artifact {
   name: string;
   artifactId: string;
   fetchUrl: string;
-  fetchUrlExpiration?: Date;
+  fetchUrlExpiration?: string;
   contentType?: string;
   sizeBytes: number;
   contents?: any;
@@ -97,7 +100,7 @@
    * @param signal An AbortSignal object.
    * @return Response body.
    */
-  private async call(service: string, method: string, request: TestVariantsQuery | listAllArtifactsQuery, signal: AbortSignal = undefined): Promise<any> {
+  private async call(service: string, method: string, request: TestVariantsQuery | listAllArtifactsQuery, signal: AbortSignal | undefined = undefined): Promise<any> {
     // Miniature implementation of pRPC protocol with JSONPB and auth.
     const authHeader = await this.getAuthorizationHeader();
     const headers = {
@@ -141,7 +144,7 @@
         'luci.resultdb.v1.ResultDB', 'QueryTestVariants', request, signal);
   }
 
-  listArtifacts(request: listAllArtifactsQuery): Promise<{artifacts: Artifact[], nextPageToken: string}> {
+  listArtifacts(request: listAllArtifactsQuery): Promise<{artifacts: Artifact[], nextPageToken?: string}> {
     return this.call('luci.resultdb.v1.ResultDB', 'ListArtifacts', request);
   }
 
@@ -162,7 +165,7 @@
     }
 
     const response = await this.queryTestVariants({
-      invocations: [build.infra.resultdb.invocation],
+      invocations: [build.infra?.resultdb?.invocation],
       predicate: {
         status: inclAdditional
             ? TestVariantStatus.UNEXPECTED_MASK
@@ -289,7 +292,7 @@
    *
    * @returns authorization header to use in requests.
    */
-  private getAuthorizationHeader(): Promise<AuthorizationHeader> {
+  getAuthorizationHeader(): Promise<AuthorizationHeader> {
     return getAuthorizationHeader(this.changeId);
   }
 }
diff --git a/web/resultdb-utils.ts b/web/resultdb-utils.ts
index fc11e7a..202bd7b 100644
--- a/web/resultdb-utils.ts
+++ b/web/resultdb-utils.ts
@@ -4,14 +4,14 @@
 
 import {getDateFromTimestamp} from './buildbucket-utils';
 import {Build} from './checks-fetcher';
-import {TestResult, TestVariant} from './resultdb-client';
+import {Artifact, TestResult, TestVariant} from './resultdb-client';
 
 // TODO(gavinmak): Make a resultdb-utils test.
 
 // Neither encodeURI() nor encodeURIComponent() encode parentheses. escape()
 // does encode parentheses, but its usage is discouraged. URL_ESCAPES is used
 // as an alternative to all the above.
-const URL_ESCAPES = {
+const URL_ESCAPES: {[key: string]: string} = {
   '(': '%28',
   ')': '%29',
 };
@@ -25,25 +25,25 @@
  * @param build Buildbucket build.
  */
 export function createLayoutResultLink(variant: TestVariant, build: Build): string {
-  if (!/^ninja:\/\/:blink_web_tests\//.test(variant.testId) ||
+  if (!/^ninja:\/\/:blink_web_tests\//.test(variant.testId!) ||
       !build.number) {
     return '';
   }
 
   let stepName = variant.results[0].result
-      .tags?.find(t => t.key === 'step_name')?.value;
+      .tags?.find(t => t['key'] === 'step_name')?.['value'];
   if (!stepName) {
     return '';
   }
 
   // Remove everything past the first ')' and encode special characters, ex:
   // 'test (patch) on some OS' becomes 'test%20%28patch%29'
-  stepName = /^([^\)]*\)?)/.exec(stepName)[1];
+  stepName = /^([^\)]*\)?)/.exec(stepName)![1];
   stepName = encodeURIComponent(stepName);
   stepName = stepName.replaceAll(URL_ESCAPE_REGEX, m => URL_ESCAPES[m]);
 
   return `https://test-results.appspot.com/data/layout_results/` +
-      `${encodeURIComponent(build.builder.builder)}/${build.number}/` +
+      `${encodeURIComponent(build.builder.builder!)}/${build.number}/` +
       `${stepName}/layout-test-results/results.html`;
 }
 
@@ -57,10 +57,10 @@
   const builder = build.builder;
   return encodeURI(`https://ci.chromium.org/ui/p/${builder.project}/` +
       `builders/${builder.bucket}/` +
-      `${encodeURIComponent(builder.builder)}/`) +
+      `${encodeURIComponent(builder.builder!)}/`) +
       `b${build.id}/test-results?q=ExactID%3A` +
-      `${encodeURIComponent(variant.testId)}+VHash%3A` +
-      `${encodeURIComponent(variant.variantHash)}&clean=`;
+      `${encodeURIComponent(variant.testId!)}+VHash%3A` +
+      `${encodeURIComponent(variant.variantHash!)}&clean=`;
 }
 
 /**
@@ -76,7 +76,7 @@
   }
 
   // If the regex doesn't match, check tags for the swarming task ID.
-  const taskId = testResult.tags?.find(t => t.key === 'swarming_task_id')?.value;
+  const taskId = testResult.tags?.find(t => t['key'] === 'swarming_task_id')?.['value'];
   if (taskId) {
     return `https://chromium-swarm.appspot.com/task?id=${taskId}`;
   }
@@ -99,7 +99,7 @@
  */
 export function getParentInv(resultName: string): string {
   const match = /^(invocations\/.+?)\//.exec(resultName);
-  return match && match[1];
+  return (match && match[1]) ?? '';
 }
 
 /**
@@ -107,7 +107,7 @@
  * returned fetchUrlExpiration is represented as the number of milliseconds
  * since the Unix Epoch.
  */
-export function getEarliestArtifactExpiration(artifacts): number {
+export function getEarliestArtifactExpiration(artifacts: Artifact[]): number {
   return Math.min(...artifacts
-      .map(a => getDateFromTimestamp(a.fetchUrlExpiration).getTime()));
+      .map(a => getDateFromTimestamp(a.fetchUrlExpiration)!.getTime()));
 }
diff --git a/web/sanitizer.ts b/web/sanitizer.ts
index 5f1cb9f..af56546 100644
--- a/web/sanitizer.ts
+++ b/web/sanitizer.ts
@@ -10,7 +10,7 @@
 */
 
 // Characters in an HTML string to escape.
-const ESCAPES = {
+const ESCAPES: {[key: string]: string} = {
   '&': '&amp;',
   '<': '&lt;',
   '>': '&gt;',
@@ -55,7 +55,7 @@
     switch (n.nodeType) {
       case 3: // TEXT_NODE
         // Print it escaped.
-        this.safeString.push(this.escape(n.textContent));
+        this.safeString.push(this.escape(n.textContent!));
         break;
 
       case 1: // ELEMENT_NODE
@@ -144,7 +144,7 @@
    * @returns The escaped HTML string.
    */
   escape(s: string): string {
-    return s.replaceAll(ESCAPE_REGEX, m => ESCAPES[m]);
+    return s.replaceAll(ESCAPE_REGEX, (m: string) => ESCAPES[m]);
   }
 }
 
diff --git a/web/test/auth_test.ts b/web/test/auth_test.ts
index 4b2a689..d34a7a2 100644
--- a/web/test/auth_test.ts
+++ b/web/test/auth_test.ts
@@ -1,18 +1,19 @@
 import {assertRejects} from './test-util';
 import './test-setup';
-import {Authenticator, initAuth, getAuthorizationHeader, resetAuthState, JWT_TOKEN_ID, JWT_TIMEOUT_MILLISECONDS, GERRIT_JWT_FLAG} from '../auth';
+import {Authenticator, initAuth, getAuthorizationHeader, resetAuthState, JWT_TOKEN_ID, JWT_TIMEOUT_MILLISECONDS, GERRIT_JWT_FLAG, Token, OAuthConfig} from '../auth';
+import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
 
 const platformJsElement = document.getElementById('platformJs') as HTMLScriptElement;
 
 suite('auth', () => {
-  let sandbox;
-  let authenticator;
-  let plugin;
-  let accessToken;
-  let jwtToken;
-  let jwtResponse;
-  let clock;
-  let changeId;
+  let sandbox: sinon.SinonSandbox;
+  let authenticator: Authenticator;
+  let plugin: PluginApi;
+  let accessToken: Token;
+  let jwtToken: Token;
+  let jwtResponse: {jwts: any};
+  let clock: sinon.SinonFakeTimers;
+  let changeId: string;
 
   setup(() => {
     sandbox = sinon.createSandbox({});
@@ -31,7 +32,7 @@
 
     window.gapi = {
       auth2: {
-        authorize: (_, resolve) => resolve(accessToken),
+        authorize: (_: any, resolve: any) => resolve(accessToken),
       },
     };
 
@@ -43,12 +44,12 @@
       }
     };
 
-    changeId = 123;
+    changeId = '123';
     plugin = {
       restApi: () => ({
         get: () => jwtResponse
       })
-    }
+    } as unknown as PluginApi;
 
     // Test with Mendel flag enabled
     if (!window.ENABLED_EXPERIMENTS) {
@@ -99,7 +100,7 @@
   test('getToken rejects when authorize times out', async () => {
     jwtResponse.jwts = {};
     const forever = new Promise(() => {});
-    sandbox.stub(gapi.auth2, 'authorize').callsFake(forever);
+    sandbox.stub(gapi.auth2, 'authorize').callsFake(forever as any);
     assertRejects(authenticator.getToken(changeId), 'timeout');
   });
 
@@ -107,7 +108,7 @@
     sandbox.spy(plugin, 'restApi');
     const actualToken1 = await authenticator.getToken(changeId);
     const actualToken2 = await authenticator.getToken(changeId);
-    assert.isTrue(plugin.restApi.calledOnce);
+    assert.isTrue((plugin.restApi as any).calledOnce);
     assert.deepEqual(actualToken1, jwtToken);
     assert.deepEqual(actualToken2, jwtToken);
   });
@@ -120,15 +121,15 @@
     ]);
     assert.deepEqual(actualToken1, jwtToken);
     assert.deepEqual(actualToken2, jwtToken);
-    assert.isTrue(plugin.restApi.calledOnce);
+    assert.isTrue((plugin.restApi as any).calledOnce);
   });
 });
 
 suite('initAuth', () => {
-  let sandbox;
-  let plugin;
-  let loggedIn;
-  let cfg;
+  let sandbox: sinon.SinonSandbox;
+  let plugin: PluginApi;
+  let loggedIn: boolean;
+  let cfg: OAuthConfig;
   const accessToken = 'token';
 
   setup(() => {
@@ -143,21 +144,22 @@
         getLoggedIn: async () => loggedIn,
         get: async (_) => cfg,
       }),
-    };
+    } as PluginApi;
 
     window.gapi = {
       auth2: {
-        init: (resolve) => resolve(),
+        init: (resolve: any) => resolve(),
       },
       config: {
         get: () => null,
         update: () => {},
       },
-      load: (_, resolve) => resolve(),
+      load: (_: any, resolve: any) => resolve(),
     };
 
-    sandbox.stub(Authenticator.prototype, 'getToken').callsFake(() => ({
+    sandbox.stub(Authenticator.prototype, 'getToken').callsFake(async () => ({
       access_token: accessToken,
+      expires_at: 0,
     }));
   });
 
diff --git a/web/test/buildbucket-client_test.ts b/web/test/buildbucket-client_test.ts
index a29d2bf..bef0625 100644
--- a/web/test/buildbucket-client_test.ts
+++ b/web/test/buildbucket-client_test.ts
@@ -1,11 +1,11 @@
 import '../cache-object';
 import {
-    BuildbucketV2Client,
+    BuildbucketV2Client, BuildRequests,
   } from '../buildbucket-client';
 
 suite('BuildbucketV2Client tests', () => {
-  let sandbox;
-  let client;
+  let sandbox: sinon.SinonSandbox;
+  let client: BuildbucketV2Client;
 
   setup(() => {
     sandbox = sinon.createSandbox();
@@ -24,16 +24,23 @@
     sandbox.stub(window, 'fetch').returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify(expected)}`,
-    }));
+    } as Response));
 
-    const req = {
-      searchBuilds: {
-        predicate: {
-          builder: {
-            project: 'chromium',
+    const req: BuildRequests = {
+      requests: [
+        {
+          searchBuilds: {
+            pageSize: 100,
+            predicate: {
+              includeExperimental: false,
+              gerritChanges: [],
+              builder: {
+                project: 'chromium',
+              },
+            },
           },
-        },
-      },
+        }
+      ]
     };
     const actual = await client.batch(req);
     assert.deepEqual(actual, expected);
diff --git a/web/test/buildbucket-utils_test.ts b/web/test/buildbucket-utils_test.ts
index eff8cc2..a0f399b 100644
--- a/web/test/buildbucket-utils_test.ts
+++ b/web/test/buildbucket-utils_test.ts
@@ -19,6 +19,7 @@
     showRetryFailed,
   } from '../buildbucket-utils';
 import {ChangeInfo, ChangeStatus, NumericChangeId, RepoName, RevisionInfo, RevisionKind} from '@gerritcodereview/typescript-api/rest-api';
+import { Build } from '../checks-fetcher.js';
 
 suite('buildbucket utils', () => {
   test('compareBuildIds sorts build ID strings in decreasing order', () => {
@@ -31,24 +32,23 @@
   });
 
   test('isExperimental returns true for experimental builds', () => {
-    assert.isFalse(isExperimental({}));
     assert.isTrue(isExperimental(
-      {input: {experiments: ['luci.non_production']}}
+      {input: {experiments: ['luci.non_production']}} as Build
     ));
     assert.isFalse(isExperimental(
-      {input: {experiments: ['luci.use_realms']}}
+      {input: {experiments: ['luci.use_realms']}} as Build
     ));
     assert.isFalse(isExperimental(
-      {input: {experiments: []}}
+      { input: { experiments: [] } } as unknown as Build
     ));
     assert.isTrue(isExperimental(
-      {tags: [{key: 'cq_experimental', value: 'true'}]}
+      {tags: [{key: 'cq_experimental', value: 'true'}]} as Build
     ));
     assert.isFalse(isExperimental(
-      {tags: [{key: 'cq_experimental', value: 'false'}]}
+      {tags: [{key: 'cq_experimental', value: 'false'}]} as Build
     ));
     assert.isFalse(isExperimental(
-      {tags: [{key: 'foo', value: 'true'}]}
+      {tags: [{key: 'foo', value: 'true'}]} as Build
     ));
   });
 
@@ -67,201 +67,111 @@
   test('showChooseTryjobs depends on config, change, and user status', () => {
     assert.isTrue(showChooseTryjobs({buckets: [{name: 'foo'}]}, true, {
       status: ChangeStatus.NEW,
-      id: undefined,
-      project: undefined,
-      branch: undefined,
-      change_id: undefined,
-      subject: '',
-      created: undefined,
-      updated: undefined,
-      insertions: 0,
-      deletions: 0,
-      _number: undefined,
-      owner: undefined,
-      reviewers: undefined
-    }));
+      id: '',
+      project: '',
+      branch: '',
+    } as ChangeInfo));
     assert.isFalse(showChooseTryjobs(null, true, {
       status: ChangeStatus.NEW,
-      id: undefined,
-      project: undefined,
-      branch: undefined,
-      change_id: undefined,
-      subject: '',
-      created: undefined,
-      updated: undefined,
-      insertions: 0,
-      deletions: 0,
-      _number: undefined,
-      owner: undefined,
-      reviewers: undefined
-    }));
+      id: '',
+      project: '',
+      branch: '',
+    } as ChangeInfo));
     assert.isFalse(showChooseTryjobs({buckets: []}, true, {
       status: ChangeStatus.NEW,
-      id: undefined,
-      project: undefined,
-      branch: undefined,
-      change_id: undefined,
-      subject: '',
-      created: undefined,
-      updated: undefined,
-      insertions: 0,
-      deletions: 0,
-      _number: undefined,
-      owner: undefined,
-      reviewers: undefined
-    }));
+      id: '',
+      project: '',
+      branch: '',
+    } as ChangeInfo));
     assert.isFalse(showChooseTryjobs({buckets: [{name: 'foo'}]}, false, {
       status: ChangeStatus.NEW,
-      id: undefined,
-      project: undefined,
-      branch: undefined,
-      change_id: undefined,
-      subject: '',
-      created: undefined,
-      updated: undefined,
-      insertions: 0,
-      deletions: 0,
-      _number: undefined,
-      owner: undefined,
-      reviewers: undefined
-    }));
+      id: '',
+      project: '',
+      branch: '',
+    } as ChangeInfo));
     assert.isFalse(showChooseTryjobs({buckets: [{name: 'foo'}]}, true, {
       status: ChangeStatus.MERGED,
-      id: undefined,
-      project: undefined,
-      branch: undefined,
-      change_id: undefined,
-      subject: '',
-      created: undefined,
-      updated: undefined,
-      insertions: 0,
-      deletions: 0,
-      _number: undefined,
-      owner: undefined,
-      reviewers: undefined
-    }));
+      id: '',
+      project: '',
+      branch: '',
+    } as ChangeInfo));
   });
 
   test('showRetryFailed depends on config, change, and user status', () => {
     assert.isTrue(showRetryFailed({hideRetryButton: false}, true, {
       status: ChangeStatus.NEW,
-      id: undefined,
-      project: undefined,
-      branch: undefined,
-      change_id: undefined,
-      subject: '',
-      created: undefined,
-      updated: undefined,
-      insertions: 0,
-      deletions: 0,
-      _number: undefined,
-      owner: undefined,
-      reviewers: undefined
-    }));
+      id: '',
+      project: '',
+      branch: '',
+    } as ChangeInfo));
     assert.isFalse(showRetryFailed({hideRetryButton: true}, true, {
       status: ChangeStatus.NEW,
-      id: undefined,
-      project: undefined,
-      branch: undefined,
-      change_id: undefined,
-      subject: '',
-      created: undefined,
-      updated: undefined,
-      insertions: 0,
-      deletions: 0,
-      _number: undefined,
-      owner: undefined,
-      reviewers: undefined
-    }));
+      id: '',
+      project: '',
+      branch: '',
+    } as ChangeInfo));
     assert.isFalse(showRetryFailed(null, true, {
       status: ChangeStatus.NEW,
-      id: undefined,
-      project: undefined,
-      branch: undefined,
-      change_id: undefined,
-      subject: '',
-      created: undefined,
-      updated: undefined,
-      insertions: 0,
-      deletions: 0,
-      _number: undefined,
-      owner: undefined,
-      reviewers: undefined
-    }));
+      id: '',
+      project: '',
+      branch: '',
+    } as ChangeInfo));
     assert.isFalse(showRetryFailed({hideRetryButton: false}, false, {
       status: ChangeStatus.NEW,
-      id: undefined,
-      project: undefined,
-      branch: undefined,
-      change_id: undefined,
-      subject: '',
-      created: undefined,
-      updated: undefined,
-      insertions: 0,
-      deletions: 0,
-      _number: undefined,
-      owner: undefined,
-      reviewers: undefined
-    }));
+      id: '',
+      project: '',
+      branch: '',
+    } as ChangeInfo));
     assert.isFalse(showRetryFailed({hideRetryButton: false}, true, {
       status: ChangeStatus.MERGED,
-      id: undefined,
-      project: undefined,
-      branch: undefined,
-      change_id: undefined,
-      subject: '',
-      created: undefined,
-      updated: undefined,
-      insertions: 0,
-      deletions: 0,
-      _number: undefined,
-      owner: undefined,
-      reviewers: undefined
-    }));
+      id: '',
+      project: '',
+      branch: '',
+    } as ChangeInfo));
   });
 
   test('shouldSkipRetry returns true for skipped builds', () => {
     assert.isTrue(shouldSkipRetry(
-      {tags: [{key: 'skip-retry-in-gerrit', value: 'true'}]}
+      {tags: [{key: 'skip-retry-in-gerrit', value: 'true'}]} as Build
     ));
     assert.isFalse(shouldSkipRetry(
-      {tags: [{key: 'skip-retry-in-gerrit', value: 'false'}]}
+      {tags: [{key: 'skip-retry-in-gerrit', value: 'false'}]} as Build
     ));
     assert.isFalse(shouldSkipRetry(
-      {tags: [{key: 'foo', value: 'true'}]}
+      {tags: [{key: 'foo', value: 'true'}]} as Build
     ));
   });
 
   test('hasHideInGerritTag returns true for hidden builds', () => {
     assert.isTrue(hasHideInGerritTag(
-      {tags: [{key: 'hide-in-gerrit', value: 'true'}]}
+      {tags: [{key: 'hide-in-gerrit', value: 'true'}]} as Build
     ));
     assert.isTrue(hasHideInGerritTag(
-      {tags: [{key: 'hide-in-gerrit', value: 'pointless'}]}
+      {tags: [{key: 'hide-in-gerrit', value: 'pointless'}]} as Build
     ));
     assert.isFalse(hasHideInGerritTag(
-      {tags: [{key: 'hide-in-gerrit', value: 'false'}]}
+      {tags: [{key: 'hide-in-gerrit', value: 'false'}]} as Build
     ));
     assert.isFalse(hasHideInGerritTag(
-      {tags: [{key: 'foo', value: 'true'}]}
+      {tags: [{key: 'foo', value: 'true'}]} as Build
     ));
   });
 
   test('hasHideTestResultsInGerritTag returns true for tagged builds', () => {
     assert.isFalse(hasHideTestResultsInGerritTag(
-      {tags: []}
+      { tags: [] } as unknown as Build
     ));
     assert.isTrue(hasHideTestResultsInGerritTag(
-      {tags: [{key: 'hide-test-results-in-gerrit', value: 'true'}]}
+      {tags: [{key: 'hide-test-results-in-gerrit', value: 'true'}]} as Build
     ));
     assert.isTrue(hasHideTestResultsInGerritTag(
-      {tags: [{key: 'hide-test-results-in-gerrit', value: 'pointless'}]}
+      {tags: [{key: 'hide-test-results-in-gerrit', value: 'pointless'}]} as Build
     ));
     assert.isFalse(hasHideTestResultsInGerritTag(
-      {tags: [{key: 'hide-test-results-in-gerrit', value: 'false'}]}
+      {tags: [{key: 'hide-test-results-in-gerrit', value: 'false'}]} as Build
     ));
     assert.isFalse(hasHideTestResultsInGerritTag(
-      {tags: [{key: 'foo', value: 'true'}]}
+      {tags: [{key: 'foo', value: 'true'}]} as Build
     ));
   });
 
@@ -302,13 +212,13 @@
   });
 
   test('getFailedBuilders with one failed build', () => {
-    const builds = [
+    const builds: Build[] = [
       {
         id: '1',
         status: BuildStatus.FAILURE,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'cq_experimental', value: 'false'}],
-      },
+      } as Build,
     ];
     assert.deepEqual(getFailedBuilders(builds), [
       {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
@@ -316,43 +226,43 @@
   });
 
   test('getFailedBuilders with an experimental build', () => {
-    const builds = [
+    const builds: Build[] = [
       {
         id: '1',
         status: BuildStatus.FAILURE,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'cq_experimental', value: 'true'}],
-      },
+      } as Build,
     ];
     assert.deepEqual(getFailedBuilders(builds), []);
   });
 
   test('getFailedBuilders with a successful build', () => {
-    const builds = [
+    const builds: Build[] = [
       {
         id: '1',
         status: BuildStatus.SUCCESS,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'cq_experimental', value: 'false'}],
-      },
+      } as Build,
     ];
     assert.deepEqual(getFailedBuilders(builds), []);
   });
 
   test('getFailedBuilders with a failed then successful build', () => {
-    const builds = [
+    const builds: Build[] = [
       {
         id: '1',
         status: BuildStatus.SUCCESS,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'cq_experimental', value: 'false'}],
-      },
+      } as Build,
       {
         id: '2',
         status: BuildStatus.FAILURE,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'cq_experimental', value: 'false'}],
-      },
+      } as Build,
     ];
     assert.deepEqual(getFailedBuilders(builds), []);
   });
@@ -361,37 +271,37 @@
     // Below, the running build is not the latest build.
     // This is unusual, but this test should verify that
     // even in this case, no builds should be retried.
-    const builds = [
+    const builds: Build[] = [
       {
         id: '1',
         status: BuildStatus.FAILURE,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'cq_experimental', value: 'false'}],
-      },
+      } as Build,
       {
         id: '2',
         status: BuildStatus.STARTED,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'cq_experimental', value: 'false'}],
-      },
+      } as Build,
     ];
     assert.deepEqual(getFailedBuilders(builds), []);
   });
 
   test('getFailedBuilders with multiple failed builds, one builder', () => {
-    const builds = [
+    const builds: Build[] = [
       {
         id: '1',
         status: BuildStatus.FAILURE,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'cq_experimental', value: 'false'}],
-      },
+      } as Build,
       {
         id: '2',
         status: BuildStatus.FAILURE,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'cq_experimental', value: 'false'}],
-      },
+      } as Build,
     ];
     assert.deepEqual(getFailedBuilders(builds), [
       {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
@@ -399,19 +309,19 @@
   });
 
   test('getFailedBuilders with multiple failed builds', () => {
-    const builds = [
+    const builds: Build[] = [
       {
         id: '1',
         status: BuildStatus.FAILURE,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'cq_experimental', value: 'false'}],
-      },
+      } as Build,
       {
         id: '2',
         status: BuildStatus.FAILURE,
         builder: {project: 'chromium', bucket: 'try', builder: 'mac-rel'},
         tags: [{key: 'cq_experimental', value: 'false'}],
-      },
+      } as Build,
     ];
     assert.deepEqual(getFailedBuilders(builds), [
       {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
@@ -420,19 +330,19 @@
   });
 
   test('getFailedBuilders with skippable failed builds', () => {
-    const builds = [
+    const builds: Build[] = [
       {
         id: '1',
         status: BuildStatus.FAILURE,
         builder: {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
         tags: [{key: 'skip-retry-in-gerrit', value: 'false'}],
-      },
+      } as Build,
       {
         id: '2',
         status: BuildStatus.FAILURE,
         builder: {project: 'chromium', bucket: 'try', builder: 'mac-rel'},
         tags: [{key: 'skip-retry-in-gerrit', value: 'true'}],
-      },
+      } as Build,
     ];
     assert.deepEqual(getFailedBuilders(builds), [
       {project: 'chromium', bucket: 'try', builder: 'linux-rel'},
@@ -460,7 +370,7 @@
       deletions: 0,
       owner: undefined,
       reviewers: undefined
-    };
+    } as unknown as ChangeInfo;
     assert.deepEqual(
       reversedRevisions(change),
       [
@@ -489,7 +399,7 @@
       deletions: 0,
       owner: undefined,
       reviewers: undefined
-    };
+    } as unknown as ChangeInfo;
     assert.isTrue(isLatestPatchset(change, 2));
     assert.isFalse(isLatestPatchset(change, 1));
   });
@@ -518,7 +428,7 @@
       deletions: 0,
       owner: undefined,
       reviewers: undefined
-    };
+    } as unknown as ChangeInfo;
     assert.deepEqual(splitPatchsetGroups(change), [
       new Set([6]),
       new Set([4, 5]),
@@ -542,23 +452,23 @@
       deletions: 0,
       owner: undefined,
       reviewers: undefined
-    };
+    } as unknown as ChangeInfo;
     assert.deepEqual(splitPatchsetGroups(change), [new Set([1])]);
   });
 
   test('getDateFromTimestamp formats timestamps correctly', () => {
     assert.strictEqual(
-      getDateFromTimestamp('2017-12-15T01:30:15.05Z').getTime(),
+      getDateFromTimestamp('2017-12-15T01:30:15.05Z')!.getTime(),
       new Date(Date.UTC(2017, 11, 15, 1, 30, 15, 50)).getTime(),
     );
 
     assert.strictEqual(
-      getDateFromTimestamp('2017-01-15T01:30:15Z').getTime(),
+      getDateFromTimestamp('2017-01-15T01:30:15Z')!.getTime(),
       new Date(Date.UTC(2017, 0, 15, 1, 30, 15, 0)).getTime(),
     );
 
     assert.strictEqual(
-      getDateFromTimestamp('2017-02-15T01:30:15.98765432Z').getTime(),
+      getDateFromTimestamp('2017-02-15T01:30:15.98765432Z')!.getTime(),
       new Date(Date.UTC(2017, 1, 15, 1, 30, 15, 988)).getTime(),
     );
   });
diff --git a/web/test/cache-object_test.ts b/web/test/cache-object_test.ts
index 35eaa52..922786e 100644
--- a/web/test/cache-object_test.ts
+++ b/web/test/cache-object_test.ts
@@ -1,7 +1,7 @@
 import {CacheObject} from '../cache-object.js';
 
 suite('CacheObject', () => {
-  let cacheObj;
+  let cacheObj: CacheObject;
 
   setup(() => {
     cacheObj = new CacheObject('cacheObjectTest');
diff --git a/web/test/checks-fetcher_test.ts b/web/test/checks-fetcher_test.ts
index e39bb6a..d9ac08f 100644
--- a/web/test/checks-fetcher_test.ts
+++ b/web/test/checks-fetcher_test.ts
@@ -8,29 +8,45 @@
   ResultDbV1Client,
   TestVariantStatus,
   TestStatus,
+  TestVariant,
   } from '../resultdb-client';
 import {
     ChecksFetcher,
     RunStatus,
     Attempt,
+    Build,
   } from '../checks-fetcher';
 import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
-import {Category, LinkIcon} from '@gerritcodereview/typescript-api/checks';
+import {Category, ChangeData, LinkIcon} from '@gerritcodereview/typescript-api/checks';
+import {
+  AccountInfo,
+  BranchName,
+  ChangeId,
+  ChangeInfo,
+  ChangeInfoId,
+  ChangeStatus,
+  GitRef,
+  NumericChangeId,
+  PatchSetNum,
+  RepoName,
+  RevisionKind,
+  Timestamp
+} from '@gerritcodereview/typescript-api/rest-api';
 
 suite('checks-fetcher tests', () => {
-  let fetcher;
-  let sandbox;
-  let plugin;
-  let changeObject;
+  let fetcher: ChecksFetcher;
+  let sandbox: sinon.SinonSandbox;
+  let plugin: PluginApi;
+  let changeObject: any;
 
   setup(() => {
     sandbox = sinon.createSandbox();
     sandbox.stub(BuildbucketV2Client.prototype, 'getAuthorizationHeader')
-        .returns('accessToken');
+        .returns(new Promise((resolve: any, _) => resolve({access_token: 'accessToken', expires_at: 0})));
     sandbox.stub(ResultDbV1Client.prototype, 'getAuthorizationHeader')
-        .returns('accessToken');
+      .returns(new Promise((resolve: any, _) => resolve({access_token: 'accessToken', expires_at: 0})));
     sandbox.stub(ChecksFetcher.prototype, 'getAuthorizationHeader')
-        .returns('accessToken');
+      .returns(new Promise((resolve: any, _) => resolve({access_token: 'accessToken', expires_at: 0})));
     plugin = {
       restApi: () => ({
         get: () => ({
@@ -39,7 +55,7 @@
         }),
       }),
       getPluginName: () => 'buildbucket',
-    };
+    } as unknown as PluginApi;
     fetcher = new ChecksFetcher(plugin, 'buildbucket.example.com', 2);
     fetcher.retryEnabled = true;
     changeObject = {
@@ -47,8 +63,8 @@
       patchsetNumber: 1,
       repo: 'foo/bar',
       changeInfo: {
-        status: 'NEW',
-        revisions: [{kind: 'REWORK', _number: 1}],
+        status: ChangeStatus.NEW,
+        revisions: [{kind: RevisionKind.REWORK, _number: 1}],
       },
     };
   });
@@ -58,24 +74,24 @@
     sandbox.restore();
   });
 
-  function stubFetch(response) {
+  function stubFetch(response: any) {
     sandbox.stub(window, 'fetch').returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify(response)}`,
-    }));
+    } as Response));
   }
 
   test('fetchTestVariants gets an empty list for no TestVariants',
       async () => {
     stubFetch({nextPageToken: 'foo'});
-    const build = {
+    const build: Build = {
       infra: {
         resultdb: {
           hostname: 'resultdb.example.com',
           invocation: '123456',
         },
       },
-    };
+    } as Build;
     assert.deepEqual(
       await fetcher.fetchTestVariants(build, false, 123456),
       [],
@@ -84,9 +100,9 @@
 
   test('fetchTestVariants can query successful builds',
       async () => {
-    const variant = {testId: 'foo'};
+    const variant = {testId: 'foo'} as TestVariant;
     stubFetch({testVariants: [variant]});
-    const build = {
+    const build: Build = {
       status: BuildStatus.SUCCESS,
       infra: {
         resultdb: {
@@ -94,7 +110,7 @@
           invocation: '123456',
         },
       },
-    };
+    } as Build;
     assert.deepEqual(
       await fetcher.fetchTestVariants(build, false, 123456),
       [],
@@ -114,12 +130,12 @@
         {status: TestVariantStatus.UNEXPECTED},
       ],
     });
-    const build = {infra: {resultdb: {hostname: 'host', invocation: 'inv'}}};
+    const build = {infra: {resultdb: {hostname: 'host', invocation: 'inv'}}} as Build;
     assert.deepEqual(
       await fetcher.fetchTestVariants(build, false, 123456),
       [
-        {status: TestVariantStatus.UNEXPECTED},
-        {status: TestVariantStatus.UNEXPECTED},
+        {status: TestVariantStatus.UNEXPECTED} as TestVariant,
+        {status: TestVariantStatus.UNEXPECTED} as TestVariant,
       ],
     );
     sinon.assert.calledOnce((fetch as any));
@@ -130,14 +146,30 @@
       responses: [
         {
           searchBuilds: {
-            builds: [{id: 1, tags:[]}],
+            builds: [{
+              id: '1',
+              tags:[],
+              builder: {},
+              status: BuildStatus.STARTED,
+              createTime: '',
+              startTime: '',
+              endTime: '',
+            }],
             nextPageToken: 'foo',
           },
         },
         {searchBuilds: {}},
         {
           searchBuilds: {
-            builds: [{id: 3, tags:[]}],
+            builds: [{
+              id: '3',
+              tags:[],
+              builder: {},
+              status: BuildStatus.STARTED,
+              createTime: '',
+              startTime: '',
+              endTime: '',
+            }],
             nextPageToken: 'bar',
           },
         },
@@ -147,7 +179,15 @@
       responses: [
         {
           searchBuilds: {
-            builds: [{id: 2, tags:[]}],
+            builds: [{
+              id: '2',
+              tags:[],
+              builder: {},
+              status: BuildStatus.STARTED,
+              createTime: '',
+              startTime: '',
+              endTime: '',
+            }],
             nextPageToken: 'baz',
           },
         },
@@ -160,19 +200,47 @@
     fetch.onCall(0).returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify(firstResponse)}`,
-    }));
+    } as Response));
     fetch.onCall(1).returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify(secondResponse)}`,
-    }));
+    } as Response));
     fetch.onCall(2).returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify(thirdResponse)}`,
-    }));
+    } as Response));
 
     assert.deepEqual(
-      await fetcher.fetchBuilds(123456, [1, 2, 3]),
-      [{id: 1, tags:[]}, {id: 3, tags:[]}, {id: 2, tags:[]}],
+      await fetcher.fetchBuilds(123456, [1, 2, 3], '', false, new AbortController()),
+      [
+        {
+          id: '1',
+          tags:[],
+          builder: {},
+          status: BuildStatus.STARTED,
+          createTime: '',
+          startTime: '',
+          endTime: '',
+        },
+        {
+          id: '3',
+          tags:[],
+          builder: {},
+          status: BuildStatus.STARTED,
+          createTime: '',
+          startTime: '',
+          endTime: '',
+        },
+        {
+          id: '2',
+          tags:[],
+          builder: {},
+          status: BuildStatus.STARTED,
+          createTime: '',
+          startTime: '',
+          endTime: '',
+        }
+      ],
     );
     sinon.assert.calledThrice(fetch);
   });
@@ -185,15 +253,39 @@
         {
           searchBuilds: {
             builds: [
-              {id: 1, tags: [nonExpTag]},
+              {
+                id: '1',
+                tags: [nonExpTag],
+                builder: {},
+                status: BuildStatus.STARTED,
+                createTime: '',
+                startTime: '',
+                endTime: '',
+              },
             ]
           },
         },
         {
           searchBuilds: {
             builds: [
-              {id: 2, tags: [expTag]},
-              {id: 3, tags: [nonExpTag]},
+              {
+                id: '2',
+                tags: [expTag],
+                builder: {},
+                status: BuildStatus.STARTED,
+                createTime: '',
+                startTime: '',
+                endTime: '',
+              },
+              {
+                id: '3',
+                tags: [nonExpTag],
+                builder: {},
+                status: BuildStatus.STARTED,
+                createTime: '',
+                startTime: '',
+                endTime: '',
+              },
             ]
           },
         },
@@ -201,19 +293,59 @@
     });
 
     assert.deepEqual(
-      await fetcher.fetchBuilds(123456, [1, 2], 'project', false),
+      await fetcher.fetchBuilds(123456, [1, 2], 'project', false, new AbortController()),
       [
-        {id: 1, tags: [nonExpTag]},
-        {id: 3, tags: [nonExpTag]},
+        {
+          id: '1',
+          tags: [nonExpTag],
+          builder: {},
+          status: BuildStatus.STARTED,
+          createTime: '',
+          startTime: '',
+          endTime: '',
+        },
+        {
+          id: '3',
+          tags: [nonExpTag],
+          builder: {},
+          status: BuildStatus.STARTED,
+          createTime: '',
+          startTime: '',
+          endTime: '',
+        },
       ],
     );
 
     assert.deepEqual(
-      await fetcher.fetchBuilds(123456, [1, 2], 'project', true),
+      await fetcher.fetchBuilds(123456, [1, 2], 'project', true, new AbortController()),
       [
-        {id: 1, tags: [nonExpTag]},
-        {id: 2, tags: [expTag]},
-        {id: 3, tags: [nonExpTag]},
+        {
+          id: '1',
+          tags: [nonExpTag],
+          builder: {},
+          status: BuildStatus.STARTED,
+          createTime: '',
+          startTime: '',
+          endTime: '',
+        },
+        {
+          id: '2',
+          tags: [expTag],
+          builder: {},
+          status: BuildStatus.STARTED,
+          createTime: '',
+          startTime: '',
+          endTime: '',
+        },
+        {
+          id: '3',
+          tags: [nonExpTag],
+          builder: {},
+          status: BuildStatus.STARTED,
+          createTime: '',
+          startTime: '',
+          endTime: '',
+        },
       ],
     );
   });
@@ -226,22 +358,54 @@
         {
           searchBuilds: {
             builds: [
-              {id: 1, tags: [nonHiddenTag]},
+              {
+                id: '1',
+                tags: [nonHiddenTag],
+                builder: {},
+                status: BuildStatus.STARTED,
+                createTime: '',
+                startTime: '',
+                endTime: '',
+              },
             ]
           },
         },
         {
           searchBuilds: {
             builds: [
-              {id: 2, tags: [hiddenTag]},
-              {id: 3, tags: [nonHiddenTag]},
+              {
+                id: '2',
+                tags: [hiddenTag],
+                builder: {},
+                status: BuildStatus.STARTED,
+                createTime: '',
+                startTime: '',
+                endTime: '',
+              },
+              {
+                id: '3',
+                tags: [nonHiddenTag],
+                builder: {},
+                status: BuildStatus.STARTED,
+                createTime: '',
+                startTime: '',
+                endTime: '',
+              },
             ]
           },
         },
         {
           searchBuilds: {
             builds: [
-              {id: 4, tags: [hiddenTag]},
+              {
+                id: '4',
+                tags: [hiddenTag],
+                builder: {},
+                status: BuildStatus.STARTED,
+                createTime: '',
+                startTime: '',
+                endTime: '',
+              },
             ]
           },
         },
@@ -249,86 +413,183 @@
     });
 
     assert.deepEqual(
-      await fetcher.fetchBuilds(123456, [1, 2], 'project'),
+      await fetcher.fetchBuilds(123456, [1, 2], 'project', true, new AbortController()),
       [
-        {id: 1, tags: [nonHiddenTag]},
-        {id: 3, tags: [nonHiddenTag]},
+        {
+          id: '1',
+          tags: [nonHiddenTag],
+          builder: {},
+          status: BuildStatus.STARTED,
+          createTime: '',
+          startTime: '',
+          endTime: '',
+        },
+        {
+          id: '3',
+          tags: [nonHiddenTag],
+          builder: {},
+          status: BuildStatus.STARTED,
+          createTime: '',
+          startTime: '',
+          endTime: '',
+        },
       ],
     );
   });
 
   test('fetchDisplayedBuilds handles equivalent patchsets', async () => {
     const stub = sandbox.stub(ChecksFetcher.prototype, 'fetchBuilds');
-    const build5 = {id: 5, tags: [{key: 'cq_experimental', value: 'false'}]};
-    const build3 = {id: 3, tags: [{key: 'cq_experimental', value: 'false'}]};
-    const build2 = {id: 2, tags: [{key: 'cq_experimental', value: 'false'}]};
+    const build5: Build = {
+      id: '5',
+      tags: [{key: 'cq_experimental', value: 'false'}],
+      builder: {},
+      status: BuildStatus.STARTED,
+      createTime: '',
+      startTime: '',
+      endTime: '',
+    };
+    const build3: Build = {
+      id: '3',
+      tags: [{key: 'cq_experimental', value: 'false'}],
+      builder: {},
+      status: BuildStatus.STARTED,
+      createTime: '',
+      startTime: '',
+      endTime: '',
+    };
+    const build2: Build = {
+      id: '2',
+      tags: [{key: 'cq_experimental', value: 'false'}],
+      builder: {},
+      status: BuildStatus.STARTED,
+      createTime: '',
+      startTime: '',
+      endTime: '',
+    };
 
-    stub.withArgs(123, [6], 'project').returns([]);
-    stub.withArgs(123, [5], 'project').returns([build5]);
-    stub.withArgs(123, [2, 3], 'project').returns([build2, build3]);
-    stub.withArgs(123, [2], 'project').returns([build2]);
+    stub.withArgs(123, [6], 'project', sinon.match.any, sinon.match.any).returns(Promise.resolve([]));
+    stub.withArgs(123, [5], 'project', sinon.match.any, sinon.match.any).returns(Promise.resolve([build5]));
+    stub.withArgs(123, [2, 3], 'project', sinon.match.any, sinon.match.any).returns(Promise.resolve([build2, build3]));
+    stub.withArgs(123, [2], 'project', sinon.match.any, sinon.match.any).returns(Promise.resolve([build2]));
 
-    const changeInfo = {
-        revisions: [
-          {kind: 'REWORK', _number: 6},
-          {kind: 'REWORK', _number: 5},
-          {kind: 'TRIVIAL_REBASE', _number: 4},
-          {kind: 'NO_CHANGE', _number: 3},
-          {kind: 'REWORK', _number: 2},
-          {kind: 'REWORK', _number: 1},
-        ],
-        status: 'MERGED',
+    const changeInfo: ChangeInfo = {
+      revisions: {
+        '1': {
+          kind: RevisionKind.REWORK,
+          _number: 6 as PatchSetNum,
+          created: '' as Timestamp,
+          uploader: '' as AccountInfo,
+          ref: '' as GitRef,
+        },
+        '2': {
+          kind: RevisionKind.REWORK,
+          _number: 5 as PatchSetNum,
+          created: '' as Timestamp,
+          uploader: '' as AccountInfo,
+          ref: '' as GitRef,
+        },
+        '3': {
+          kind: RevisionKind.TRIVIAL_REBASE,
+          _number: 4 as PatchSetNum,
+          created: '' as Timestamp,
+          uploader: '' as AccountInfo,
+          ref: '' as GitRef,
+        },
+        '4': {
+          kind: RevisionKind.NO_CHANGE,
+          _number: 3 as PatchSetNum,
+          created: '' as Timestamp,
+          uploader: '' as AccountInfo,
+          ref: '' as GitRef,
+        },
+        '5': {
+          kind: RevisionKind.REWORK,
+          _number: 2 as PatchSetNum,
+          created: '' as Timestamp,
+          uploader: '' as AccountInfo,
+          ref: '' as GitRef,
+        },
+        '6': {
+          kind: RevisionKind.REWORK,
+          _number: 1 as PatchSetNum,
+          created: '' as Timestamp,
+          uploader: '' as AccountInfo,
+          ref: '' as GitRef,
+        },
+      },
+      status: ChangeStatus.MERGED,
+      id: '' as ChangeInfoId,
+      project: '' as RepoName,
+      branch: '' as BranchName,
+      change_id: '' as ChangeId,
+      subject: '',
+      created: '' as Timestamp,
+      updated: '' as Timestamp,
+      insertions: 0,
+      deletions: 0,
+      _number: 0 as NumericChangeId,
+      owner: '' as AccountInfo,
+      reviewers: {},
     };
     fetcher = new ChecksFetcher(plugin, 'buildbucket.example.com', 2);
 
     // Fetching PS 3 gets builds 2 and 3.
     assert.deepEqual(
-      await fetcher.fetchDisplayedBuilds(123, 3, 'project', changeInfo),
+      await fetcher.fetchDisplayedBuilds(123, 3, 'project', changeInfo, new AbortController()),
       [build2, build3],
     )
 
     // Fetching PS 2 gets build 2.
     assert.deepEqual(
-      await fetcher.fetchDisplayedBuilds(123, 2, 'project', changeInfo),
+      await fetcher.fetchDisplayedBuilds(123, 2, 'project', changeInfo, new AbortController()),
       [build2],
     )
 
     // Fetching PS 6 gets build 5.
     assert.deepEqual(
-      await fetcher.fetchDisplayedBuilds(123, 6, 'project', changeInfo),
+      await fetcher.fetchDisplayedBuilds(123, 6, 'project', changeInfo, new AbortController()),
       [build5],
     )
   });
 
   test('convertBuildToRun creates no results for SCHEDULED or STARTED builds',
       () => {
-    const scheduledBuild = {
-      builder: {builder: 'foo'},
+    const scheduledBuild: Build = {
+      builder: { builder: 'foo' },
       id: '12345',
       status: BuildStatus.SCHEDULED,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
     assert.deepEqual(
       fetcher.convertBuildToRun(
-          123456, scheduledBuild, 1, 1, [{id: '123', status: BuildStatus.SCHEDULED}]).results,
+          123456, scheduledBuild, 1, 1, [{id: '123', status: BuildStatus.SCHEDULED, isQuickRun: false, build: scheduledBuild}], '').results,
       []);
 
-    const startedBuild = {
-      builder: {builder: 'foo'},
+    const startedBuild: Build = {
+      builder: { builder: 'foo' },
       id: '12345',
       status: BuildStatus.STARTED,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
     assert.deepEqual(
       fetcher.convertBuildToRun(
-          123456, startedBuild, 1, 1, [{id: '123', status: BuildStatus.STARTED}]).results,
+          123456, startedBuild, 1, 1, [{id: '123', status: BuildStatus.STARTED, isQuickRun: false, build: startedBuild}], '').results,
       []);
   });
 
   test('convertBuildToRun creates statuses for SCHEDULED and STARTED builds',
       () => {
-    const scheduledBuild = {
-      builder: {builder: 'foo'},
+    const scheduledBuild: Build = {
+      builder: { builder: 'foo' },
       id: '12345',
       status: BuildStatus.SCHEDULED,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
     assert.deepEqual(
       fetcher.convertBuildToRun(
@@ -336,14 +597,18 @@
           scheduledBuild,
           1,
           1,
-          [{id: '123', status: BuildStatus.SCHEDULED}],
+          [{id: '123', status: BuildStatus.SCHEDULED, isQuickRun: false, build: scheduledBuild}],
+          ''
       ).status,
       RunStatus.SCHEDULED);
 
-    const startedBuild = {
-      builder: {builder: 'foo'},
+    const startedBuild : Build= {
+      builder: { builder: 'foo' },
       id: '12345',
       status: BuildStatus.STARTED,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
     assert.deepEqual(
       fetcher.convertBuildToRun(
@@ -351,142 +616,184 @@
           startedBuild,
           1,
           1,
-          [{id: '123', status: BuildStatus.STARTED}],
+          [{id: '123', status: BuildStatus.STARTED, isQuickRun: false, build: scheduledBuild}],
+          ''
       ).status,
       RunStatus.RUNNING);
   });
 
   test('fetch returns only variant results for SUCCESSful builds',
       async () => {
-    sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns([{
+    sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns(Promise.resolve([{
       builder: {builder: 'foo'},
       id: '123456',
       endTime: '2017-12-15T01:30:15.05Z',
       status: BuildStatus.SUCCESS,
-      infra: {resultdb: {hostname: 'host'}},
-    }]);
+      infra: {resultdb: {hostname: 'host', invocation: ''}},
+      createTime: '',
+      startTime: ''
+    }]));
     sandbox.stub(ChecksFetcher.prototype, 'fetchTestVariants')
-        .returns(Promise.resolve([
-          {testMetadata: {name: 'test'}, results: [{result: {}}]}
-        ]));
+      .returns(Promise.resolve([{
+        testMetadata: {name: 'test'},
+        results: [{result: {name: '', tags: []}}],
+        variant: '',
+        exonerations: [],
+        status: TestVariantStatus.EXPECTED
+      }]));
 
     const res = await fetcher.fetch(changeObject);
-    const {results} = res.runs[0];
-    assert.strictEqual(results.length, 1);
-    assert.strictEqual(results[0].summary,
+    const {results} = res.runs![0];
+    assert.strictEqual(results!.length, 1);
+    assert.strictEqual(results![0].summary,
       'Test test failed but the build succeeded.',
     );
   });
 
   test('fetch does not fetch variant results for builds tagged with ' +
     'hide-test-results-in-gerrit', async () => {
-    sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns([{
+    sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns(Promise.resolve([{
       builder: {builder: 'foo'},
       id: '123456',
       endTime: '2017-12-15T01:30:15.05Z',
       status: BuildStatus.FAILURE,
-      infra: {resultdb: {hostname: 'host'}},
+      infra: {resultdb: {hostname: 'host', invocation: ''}},
       tags: [{key: 'hide-test-results-in-gerrit', value: 'true'}],
-    }]);
+      createTime: '',
+      startTime: ''
+    }]));
     const variantSpy = sandbox.spy(
         ResultDbV1Client.prototype, 'fetchTestVariants');
 
     const res = await fetcher.fetch(changeObject);
-    const {results} = res.runs[0];
-    assert.strictEqual(results.length, 1);
-    assert.strictEqual(results[0].summary,
+    const {results} = res.runs![0];
+    assert.strictEqual(results!.length, 1);
+    assert.strictEqual(results![0].summary,
       'Build foo failed.',
     );
     sinon.assert.notCalled(variantSpy);
   });
 
   test('convertBuildToRun gets Buildbucket CheckResults', () => {
-    const build = {
-      builder: {builder: 'foo'},
+    const build : Build= {
+      builder: { builder: 'foo' },
       infra: {},
       id: '123456',
       status: BuildStatus.FAILURE,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
     const run = fetcher.convertBuildToRun(
-      123456, build, 1, 1, [{id: '123456', status: BuildStatus.FAILURE}]);
-    assert.strictEqual(run.results.length, 1);
-    assert.strictEqual(run.results[0].summary, 'Build foo failed.');
+      123456, build, 1, 1, [{id: '123456', status: BuildStatus.FAILURE, isQuickRun: false, build: build}], '');
+    assert.strictEqual(run.results!.length, 1);
+    assert.strictEqual(run.results![0].summary, 'Build foo failed.');
   });
 
   test('fetch orders Buildbucket CheckResults first', async () => {
-    sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns([{
+    sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds').returns(Promise.resolve([{
       builder: {builder: 'foo'},
       id: '123456',
       infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
       status: BuildStatus.FAILURE,
-    }]);
+      createTime: '',
+      startTime: '',
+      endTime: ''
+    }]));
     sandbox.stub(ChecksFetcher.prototype, 'fetchTestVariants')
         .returns(Promise.resolve([
           {
             testMetadata: {name: 'test0'},
-            results: [{result: {}}],
+            results: [{result: {name: '', tags: []}}],
+            variant: '',
+            exonerations: [],
+            status: TestVariantStatus.EXPECTED
           },
           {
             testMetadata: {name: 'test1'},
-            results: [{result: {}}],
+            results: [{result: {name: '', tags: []}}],
+            variant: '',
+            exonerations: [],
+            status: TestVariantStatus.EXPECTED
           },
         ]));
 
     const res = await fetcher.fetch(changeObject);
-    const {results} = res.runs[0];
-    assert.strictEqual(results.length, 4);
-    assert.strictEqual(results[0].summary, 'Build foo failed.');
-    assert.strictEqual(results[3].message,
+    const {results} = res.runs![0];
+    assert.strictEqual(results!.length, 4);
+    assert.strictEqual(results![0].summary, 'Build foo failed.');
+    assert.strictEqual(results![3].message,
         'More tests may have failed. View all results on the build page.');
   });
 
   test('convertBuildToRun handles non-critical builds', () => {
-    const build = {
-      builder: {builder: 'foo'},
+    const build: Build = {
+      builder: { builder: 'foo' },
       critical: 'NO',
       infra: {},
       id: '123456',
       status: BuildStatus.FAILURE,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
     const run = fetcher.convertBuildToRun(
-        123456, build, 1, 1, [{id: '123456', status: BuildStatus.FAILURE, build}]);
-    assert.strictEqual(run.results.length, 1);
-    assert.strictEqual(run.results[0].category, Category.WARNING);
+        123456, build, 1, 1, [{id: '123456', status: BuildStatus.FAILURE, build, isQuickRun: false}], '');
+    assert.strictEqual(run.results!.length, 1);
+    assert.strictEqual(run.results![0].category, Category.WARNING);
     assert.strictEqual(
-      run.results[0].summary,
+      run.results![0].summary,
       'Non-critical build foo failed.',
     );
   });
 
   test('convertBuildToRun sets CheckName based on latest attempt', () => {
-    const build = {
-      builder: {builder: 'foo'},
+    const build: Build = {
+      builder: { builder: 'foo' },
       critical: 'NO',
       infra: {},
       id: '123456',
       status: BuildStatus.FAILURE,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
     const startedRun = fetcher.convertBuildToRun(123456, build, 1, 1, [
-      {id: '1', status: BuildStatus.FAILURE},
-      {id: '2', status: BuildStatus.FAILURE},
-      {id: '3', status: BuildStatus.STARTED},
-    ]);
+      {id: '1', status: BuildStatus.FAILURE, isQuickRun: false, build},
+      {id: '2', status: BuildStatus.FAILURE, isQuickRun: false, build},
+      {id: '3', status: BuildStatus.STARTED, isQuickRun: false, build},
+    ], '');
     assert.strictEqual(startedRun.checkName, 'foo');
 
     const infraFailedRun = fetcher.convertBuildToRun(123456, build, 2, 2, [
-      {id: '1', status: BuildStatus.FAILURE},
-      {id: '2', status: BuildStatus.INFRA_FAILURE},
-      {id: '3', status: BuildStatus.FAILURE},
-    ]);
+      {id: '1', status: BuildStatus.FAILURE, isQuickRun: false, build},
+      {id: '2', status: BuildStatus.INFRA_FAILURE, isQuickRun: false, build},
+      {id: '3', status: BuildStatus.FAILURE, isQuickRun: false, build},
+    ], '');
     assert.strictEqual(infraFailedRun.checkName, 'foo');
   });
 
   test('convertBuildToRun shows previous attempts', () => {
     fetcher.showPreviousAttempts = true;
-    const attempts = [
-      {id: '3', build: {id: '3', status: BuildStatus.INFRA_FAILURE}},
-      {id: '2', build: {id: '2', status: BuildStatus.SUCCESS}},
-      {id: '1', build: {id: '1', status: BuildStatus.FAILURE}},
+    const attempts: Attempt[] = [
+      {
+        id: '3',
+        build: {id: '3', status: BuildStatus.INFRA_FAILURE, builder: {}, createTime: '', startTime: '', endTime: ''},
+        isQuickRun: false,
+        status: BuildStatus.INFRA_FAILURE
+      },
+      {
+        id: '2',
+        build: {id: '2', status: BuildStatus.SUCCESS, builder: {}, createTime: '', startTime: '', endTime: ''},
+        isQuickRun: false,
+        status: BuildStatus.SUCCESS
+      },
+      {
+        id: '1',
+        build: {id: '1', status: BuildStatus.FAILURE, builder: {}, createTime: '', startTime: '', endTime: ''},
+        isQuickRun: false,
+        status: BuildStatus.FAILURE
+      },
     ];
 
     let run = fetcher.convertBuildToRun(
@@ -497,12 +804,15 @@
         infra: {},
         id: '1',
         status: BuildStatus.FAILURE,
-      }, 1, 1, attempts);
-    assert.strictEqual(run.results.length, 3);
+        createTime: '',
+        startTime: '',
+        endTime: ''
+      }, 1, 1, attempts, '');
+    assert.strictEqual(run.results!.length, 3);
     // Order is reversed. Show latest results first.
-    assert.strictEqual(run.results[0].summary, 'Attempt 3 failed.');
-    assert.strictEqual(run.results[1].summary, 'Attempt 2 succeeded.');
-    assert.strictEqual(run.results[2].summary, 'Attempt 1 infra failed.');
+    assert.strictEqual(run.results![0].summary, 'Attempt 3 failed.');
+    assert.strictEqual(run.results![1].summary, 'Attempt 2 succeeded.');
+    assert.strictEqual(run.results![2].summary, 'Attempt 1 infra failed.');
 
     // Attempts shown should be <= current build.
     run = fetcher.convertBuildToRun(
@@ -513,9 +823,12 @@
         infra: {},
         id: '3',
         status: BuildStatus.INFRA_FAILURE,
-      }, 1, 1, attempts);
-    assert.strictEqual(run.results.length, 1);
-    assert.strictEqual(run.results[0].summary, 'Attempt 1 infra failed.');
+        createTime: '',
+        startTime: '',
+        endTime: ''
+      }, 1, 1, attempts, '');
+    assert.strictEqual(run.results!.length, 1);
+    assert.strictEqual(run.results![0].summary, 'Attempt 1 infra failed.');
   });
 
   test('convertBuildToRun uses max status if showing previous attempts',
@@ -530,6 +843,9 @@
         infra: {},
         id: '1',
         status: BuildStatus.SUCCESS,
+        createTime: '',
+        startTime: '',
+        endTime: ''
       },
       1,
       1,
@@ -537,22 +853,25 @@
         {
           id: '3',
           status: BuildStatus.FAILURE,
-          build: {id: '3', status: BuildStatus.FAILURE},
+          build: {id: '3', status: BuildStatus.FAILURE, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '2',
           status: BuildStatus.CANCELED,
-          build: {id: '2', status: BuildStatus.CANCELED},
+          build: {id: '2', status: BuildStatus.CANCELED, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '1',
           status: BuildStatus.SUCCESS,
-          build: {id: '1', status: BuildStatus.SUCCESS},
+          build: {id: '1', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
-      ],
+      ], ''
     );
-    assert.strictEqual(run.results.length, 3);
-    for (const res of run.results) {
+    assert.strictEqual(run.results!.length, 3);
+    for (const res of run.results!) {
       assert.strictEqual(res.category, Category.ERROR);
     }
 
@@ -564,6 +883,9 @@
         infra: {},
         id: '1',
         status: BuildStatus.SUCCESS,
+        createTime: '',
+        startTime: '',
+        endTime: ''
       },
       1,
       1,
@@ -571,21 +893,25 @@
         {
           id: '3',
           status: BuildStatus.FAILURE,
-          build: {id: '3', status: BuildStatus.FAILURE, critical: 'NO'}},
+          build: {id: '3', status: BuildStatus.FAILURE, critical: 'NO', createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
+        },
         {
           id: '2',
           status: BuildStatus.CANCELED,
-          build: {id: '2', status: BuildStatus.CANCELED},
+          build: {id: '2', status: BuildStatus.CANCELED, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '1',
           status: BuildStatus.SUCCESS,
-          build: {id: '1', status: BuildStatus.SUCCESS},
+          build: {id: '1', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
-      ],
+      ], ''
     );
-    assert.strictEqual(run.results.length, 3);
-    for (const res of run.results) {
+    assert.strictEqual(run.results!.length, 3);
+    for (const res of run.results!) {
       assert.strictEqual(res.category, Category.WARNING);
     }
 
@@ -597,6 +923,9 @@
         infra: {},
         id: '1',
         status: BuildStatus.SUCCESS,
+        createTime: '',
+        startTime: '',
+        endTime: ''
       },
       1,
       1,
@@ -604,22 +933,25 @@
         {
           id: '3',
           status: BuildStatus.SUCCESS,
-          build: {id: '3', status: BuildStatus.SUCCESS},
+          build: {id: '3', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '2',
           status: BuildStatus.CANCELED,
-          build: {id: '2', status: BuildStatus.CANCELED},
+          build: {id: '2', status: BuildStatus.CANCELED, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '1',
           status: BuildStatus.SUCCESS,
-          build: {id: '1', status: BuildStatus.SUCCESS},
+          build: {id: '1', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
-      ],
+      ], ''
     );
-    assert.strictEqual(run.results.length, 3);
-    for (const res of run.results) {
+    assert.strictEqual(run.results!.length, 3);
+    for (const res of run.results!) {
       assert.strictEqual(res.category, Category.INFO);
     }
 
@@ -631,6 +963,9 @@
         infra: {},
         id: '1',
         status: BuildStatus.SUCCESS,
+        createTime: '',
+        startTime: '',
+        endTime: ''
       },
       1,
       1,
@@ -638,21 +973,24 @@
         {
           id: '3',
           status: BuildStatus.SUCCESS,
-          build: {id: '3', status: BuildStatus.SUCCESS},
+          build: {id: '3', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '2',
           status: BuildStatus.SUCCESS,
-          build: {id: '2', status: BuildStatus.SUCCESS},
+          build: {id: '2', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '1',
           status: BuildStatus.SUCCESS,
-          build: {id: '1', status: BuildStatus.SUCCESS},
+          build: {id: '1', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
-      ],
+      ], ''
     );
-    assert.strictEqual(run.results.length, 0);
+    assert.strictEqual(run.results!.length, 0);
   });
 
   test('convertBuildToRun uses latest status if not showing previous attempts',
@@ -667,6 +1005,9 @@
         infra: {},
         id: '1',
         status: BuildStatus.SUCCESS,
+        createTime: '',
+        startTime: '',
+        endTime: ''
       },
       1,
       1,
@@ -674,21 +1015,24 @@
         {
           id: '3',
           status: BuildStatus.FAILURE,
-          build: {id: '3', status: BuildStatus.FAILURE},
+          build: {id: '3', status: BuildStatus.FAILURE, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '2',
           status: BuildStatus.CANCELED,
-          build: {id: '2', status: BuildStatus.CANCELED},
+          build: {id: '2', status: BuildStatus.CANCELED, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '1',
           status: BuildStatus.SUCCESS,
-          build: {id: '1', status: BuildStatus.SUCCESS},
+          build: {id: '1', status: BuildStatus.SUCCESS, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
-      ],
+      ], ''
     );
-    assert.strictEqual(run.results.length, 0);
+    assert.strictEqual(run.results!.length, 0);
 
     // Canceled runs should have a result.
     run = fetcher.convertBuildToRun(
@@ -698,6 +1042,9 @@
         infra: {},
         id: '1',
         status: BuildStatus.CANCELED,
+        createTime: '',
+        startTime: '',
+        endTime: ''
       },
       1,
       1,
@@ -705,27 +1052,30 @@
         {
           id: '3',
           status: BuildStatus.FAILURE,
-          build: {id: '3', status: BuildStatus.FAILURE},
+          build: {id: '3', status: BuildStatus.FAILURE, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '2',
           status: BuildStatus.INFRA_FAILURE,
-          build: {id: '2', status: BuildStatus.INFRA_FAILURE},
+          build: {id: '2', status: BuildStatus.INFRA_FAILURE, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
         {
           id: '1',
           status: BuildStatus.CANCELED,
-          build: {id: '1', status: BuildStatus.CANCELED},
+          build: {id: '1', status: BuildStatus.CANCELED, createTime: '', startTime: '', endTime: '', builder: {}},
+          isQuickRun: false,
         },
-      ],
+      ], ''
     );
-    assert.strictEqual(run.results.length, 1);
-    assert.strictEqual(run.results[0].category, Category.INFO);
+    assert.strictEqual(run.results!.length, 1);
+    assert.strictEqual(run.results![0].category, Category.INFO);
   });
 
   test('convertBuildToRun handles RTS builds', () => {
-    const build = {
-      builder: {builder: 'foo'},
+    const build: Build = {
+      builder: { builder: 'foo' },
       critical: 'NO',
       infra: {},
       id: '1',
@@ -734,88 +1084,102 @@
         properties: {
           rts_was_used: true,
         }
-      }
+      },
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
     let run = fetcher.convertBuildToRun(123456, build, 1, 1, [
-      {id: '1', status: BuildStatus.INFRA_FAILURE, isQuickRun: true},
-    ]);
+      {id: '1', status: BuildStatus.INFRA_FAILURE, isQuickRun: true, build},
+    ], '');
     assert.strictEqual(run.checkName, '🟪\u2005\u2005🚀\u2005\u2005foo');
-    assert.isTrue(run.statusDescription.includes('Quick Run'));
-    assert.isTrue(run.results[0].tags.map(t => t.name)
+    assert.isTrue(run.statusDescription!.includes('Quick Run'));
+    assert.isTrue(run.results![0].tags!.map(t => t.name)
         .includes('🚀\u2005\u2005Quick Run'));
 
     // The last attempt should be used for check run naming.
     run = fetcher.convertBuildToRun(123456, build, 1, 1, [
-      {id: '2', status: BuildStatus.INFRA_FAILURE, isQuickRun: true},
-      {id: '1', status: BuildStatus.INFRA_FAILURE, isQuickRun: false},
-    ]);
+      {id: '2', status: BuildStatus.INFRA_FAILURE, isQuickRun: true, build},
+      {id: '1', status: BuildStatus.INFRA_FAILURE, isQuickRun: false, build},
+    ], '');
     assert.strictEqual(run.checkName, '🟪\u2005\u2005foo');
   });
 
   test('convertBuildToRun tags CheckResults for experimental builds', () => {
-    const baseBuild = {
-      builder: {builder: 'foo'},
+    const baseBuild: Build = {
+      builder: { builder: 'foo' },
       id: '123456',
-      infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
+      infra: { resultdb: { hostname: 'host', invocation: 'inv' } },
       status: BuildStatus.FAILURE,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
-    const attempts = [{id: '123456', status: BuildStatus.FAILURE}];
+    const attempts: Attempt[] = [{id: '123456', status: BuildStatus.FAILURE, isQuickRun: false, build: baseBuild}];
 
-    const expBuild = Object.assign(baseBuild,
+    const expBuild: Build = Object.assign(baseBuild,
       {tags: [{key: 'cq_experimental', value: 'true'}]});
-    const expRun = fetcher.convertBuildToRun(123456, expBuild, 1, 1, attempts);
-    expRun.results.forEach((result) => {
+    const expRun = fetcher.convertBuildToRun(123456, expBuild, 1, 1, attempts, '');
+    expRun.results!.forEach((result) => {
       assert.isTrue(
-          result.tags.map(t => t.name).includes('Experimental'));
+          result.tags!.map(t => t.name).includes('Experimental'));
     });
 
     const nonExpBuild = Object.assign(baseBuild,
       {tags: [{key: 'cq_experimental', value: 'false'}]});
-    const nonExpRun = fetcher.convertBuildToRun(123456, nonExpBuild, 1, 1, attempts);
-    nonExpRun.results.forEach((result) => {
+    const nonExpRun = fetcher.convertBuildToRun(123456, nonExpBuild, 1, 1, attempts, '');
+    nonExpRun.results!.forEach((result) => {
       assert.isFalse(
-          result.tags.map(t => t.name).includes('Experimental'));
+          result.tags!.map(t => t.name).includes('Experimental'));
     });
   });
 
   test('convertBuildToRun tags infra failures', () => {
-    const baseBuild = {
+    const baseBuild: Build = {
       builder: {builder: 'foo'},
       id: '123456',
       infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
+      status: BuildStatus.FAILURE,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
-    const attempts = [{id: '123456', status: BuildStatus.INFRA_FAILURE}];
+    const attempts: Attempt[] = [{id: '123456', status: BuildStatus.INFRA_FAILURE, isQuickRun: false, build: baseBuild}];
 
     const infraFailedBuild = Object.assign(
         baseBuild, {status: BuildStatus.INFRA_FAILURE});
     const infraRun = fetcher.convertBuildToRun(
-      123456, infraFailedBuild, 1, 1, attempts);
+      123456, infraFailedBuild, 1, 1, attempts, '');
     assert.strictEqual(infraRun.checkName, '🟪\u2005\u2005foo');
-    infraRun.results.forEach((r) => {
-      assert.isTrue(r.tags.map(t => t.name).includes('Infra Failure'));
+    infraRun.results!.forEach((r) => {
+      assert.isTrue(r.tags!.map(t => t.name).includes('Infra Failure'));
     });
 
     const failedBuild = Object.assign(baseBuild, {status: BuildStatus.FAILURE});
     const nonInfraRun = fetcher.convertBuildToRun(
-      123456, failedBuild, 1, 1, attempts);
-    nonInfraRun.results.forEach((r) => {
-      assert.isFalse(r.tags.map(t => t.name).includes('Infra Failure'));
+      123456, failedBuild, 1, 1, attempts, '');
+    nonInfraRun.results!.forEach((r) => {
+      assert.isFalse(r.tags!.map(t => t.name).includes('Infra Failure'));
     });
   });
 
   test('convertBuildToRun categorizes failed test results as WARNING if ' +
       'build succeeded', async () => {
-    const build = {
-      builder: {builder: 'foo'},
+    const build: Build = {
+      builder: { builder: 'foo' },
       id: '123456',
       endTime: '2017-12-15T01:30:15.05Z',
       status: BuildStatus.SUCCESS,
-      infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
+      infra: { resultdb: { hostname: 'host', invocation: 'inv' } },
+      createTime: '',
+      startTime: ''
     };
-    const variants = [{
+    const variants: TestVariant[] = [{
       testMetadata: {name: 'test'},
       status: TestVariantStatus.UNEXPECTED,
-      results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>'}}],
+      results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>', name: '', tags: []}}],
+      variant: '',
+      exonerations: [],
     }];
 
     const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456);
@@ -831,29 +1195,37 @@
   });
 
   test('convertVariantsToCheckResults handles flaky results', async () => {
-    const build = {
-      builder: {builder: 'foo'},
+    const build: Build = {
+      builder: { builder: 'foo' },
       id: '123456',
       status: BuildStatus.FAILURE,
       endTime: '2017-12-15T01:30:15.05Z',
-      infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
+      infra: { resultdb: { hostname: 'host', invocation: 'inv' } },
+      createTime: '',
+      startTime: ''
     };
-    const variants = [{
+    const variants: TestVariant[] = [{
       testMetadata: {name: 'foo-test'},
       status: TestVariantStatus.FLAKY,
       results: [
         {
           result: {
             status: TestStatus.FAIL,
+            name: '',
+            tags: []
           },
         },
         {
           result: {
             status: TestStatus.PASS,
             expected: true,
+            name: '',
+            tags: []
           },
         },
       ],
+      variant: '',
+      exonerations: [],
     }];
 
     const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456);
@@ -875,17 +1247,21 @@
   });
 
   test('convertVariantsToCheckResults handles exonerated results', async () => {
-    const build = {
-      builder: {builder: 'foo'},
+    const build: Build = {
+      builder: { builder: 'foo' },
       id: '123456',
       status: BuildStatus.FAILURE,
       endTime: '2017-12-15T01:30:15.05Z',
-      infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
+      infra: { resultdb: { hostname: 'host', invocation: 'inv' } },
+      createTime: '',
+      startTime: ''
     };
-    const variants = [{
+    const variants: TestVariant[] = [{
       testMetadata: {name: 'foo-test'},
       status: TestVariantStatus.EXONERATED,
-      results: [{result: {status: TestStatus.PASS}}],
+      results: [{result: {status: TestStatus.PASS, name: '', tags: []}}],
+      variant: '',
+      exonerations: [],
     }];
 
     const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456);
@@ -896,17 +1272,21 @@
   });
 
   test('convertVariantsToCheckResults handles unexpectedly skipped results', async () => {
-    const build = {
-      builder: {builder: 'foo'},
+    const build: Build = {
+      builder: { builder: 'foo' },
       id: '123456',
       status: BuildStatus.FAILURE,
       endTime: '2017-12-15T01:30:15.05Z',
-      infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
+      infra: { resultdb: { hostname: 'host', invocation: 'inv' } },
+      createTime: '',
+      startTime: ''
     };
-    const variants = [{
+    const variants: TestVariant[] = [{
       testMetadata: {name: 'foo-test'},
       status: TestVariantStatus.UNEXPECTEDLY_SKIPPED,
-      results: [{result: {status: TestStatus.SKIP}}],
+      results: [{result: {status: TestStatus.SKIP, name: '', tags: []}}],
+      variant: '',
+      exonerations: [],
     }];
 
     const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456);
@@ -917,22 +1297,29 @@
   });
 
   test('convertVariantsToCheckResults categorizes preliminary results', async () => {
-    const build = {
-      builder: {builder: 'foo'},
+    const build: Build = {
+      builder: { builder: 'foo' },
       id: '123456',
       status: BuildStatus.STARTED,
-      infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
+      infra: { resultdb: { hostname: 'host', invocation: 'inv' } },
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
-    const variants = [
+    const variants: TestVariant[] = [
       {
         testMetadata: {name: 'test0'},
         status: TestVariantStatus.UNEXPECTED,
-        results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>'}}],
+        results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>', name: '', tags: []}}],
+        variant: '',
+        exonerations: [],
       },
       {
         testMetadata: {name: 'test1'},
         status: TestVariantStatus.UNEXPECTED,
-        results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>'}}],
+        results: [{result: {status: TestStatus.FAIL, summaryHtml: '<p>foo</p>', name: '', tags: []}}],
+        variant: '',
+        exonerations: [],
       },
     ];
 
@@ -945,111 +1332,129 @@
       checkResult.summary,
       'Test test0 failed.',
     );
-    assert.isTrue(checkResult.tags
+    assert.isTrue(checkResult.tags!
         .map(t => t.name).includes('Preliminary'));
     assert.strictEqual(extraResult.category, Category.INFO);
-    assert.isTrue(extraResult.tags.map(t => t.name).includes('Preliminary'));
+    assert.isTrue(extraResult.tags!.map(t => t.name).includes('Preliminary'));
   });
 
   test('convertVariantsToCheckResults tags results with variant def', async () => {
-    const build = {
-      builder: {builder: 'foo'},
+    const build: Build = {
+      builder: { builder: 'foo' },
       id: '123456',
       endTime: '2017-12-15T01:30:15.05Z',
       status: BuildStatus.FAILURE,
-      infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
+      infra: { resultdb: { hostname: 'host', invocation: 'inv' } },
+      createTime: '',
+      startTime: ''
     };
-    const variants = [
+    const variants: TestVariant[] = [
       {
         variant: {def: {test_suite: 'suite_foo', gpu: 'gpu_foo'}},
         testMetadata: {name: 'test2'},
         status: TestVariantStatus.UNEXPECTED,
-        results: [{result: {status: TestStatus.FAIL}}],
+        results: [{result: {status: TestStatus.FAIL, name: '', tags: []}}],
+        exonerations: [],
       },
       {
         variant: {def: {test_suite: 'suite_foo', build_target: 'build_foo'}},
         testMetadata: {name: 'test1'},
         status: TestVariantStatus.UNEXPECTED,
-        results: [{result: {status: TestStatus.FAIL}}],
+        results: [{result: {status: TestStatus.FAIL, name: '', tags: []}}],
+        exonerations: [],
       },
       {
         variant: {def: {test_suite: 'suite_bar', gpu: 'gpu_bar'}},
         testMetadata: {name: 'test1'},
         status: TestVariantStatus.UNEXPECTED,
-        results: [{result: {status: TestStatus.FAIL}}],
+        results: [{result: {status: TestStatus.FAIL, name: '', tags: []}}],
+        exonerations: [],
       },
       {
         variant: {def: {}},
         testMetadata: {name: 'test0'},
         status: TestVariantStatus.UNEXPECTED,
-        results: [{result: {status: TestStatus.FAIL}}],
+        results: [{result: {status: TestStatus.FAIL, name: '', tags: []}}],
+        exonerations: [],
       },
     ];
 
     const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456);
     assert.isTrue(results[0].summary.includes('test0'));
-    assert.strictEqual(results[0].tags.length, 0);
+    assert.strictEqual(results[0].tags!.length, 0);
 
     assert.isTrue(results[1].summary.includes('test1'));
-    assert.strictEqual(results[1].tags.length, 2);
-    assert.isTrue(results[1].tags[0].tooltip.includes('suite_bar'));
-    assert.isTrue(results[1].tags[1].tooltip.includes('gpu_bar'));
+    assert.strictEqual(results[1].tags!.length, 2);
+    assert.isTrue(results[1].tags![0].tooltip!.includes('suite_bar'));
+    assert.isTrue(results[1].tags![1].tooltip!.includes('gpu_bar'));
 
     assert.isTrue(results[2].summary.includes('test1'));
-    assert.strictEqual(results[2].tags.length, 2);
-    assert.isTrue(results[2].tags[0].tooltip.includes('suite_foo'));
-    assert.isTrue(results[2].tags[1].tooltip.includes('build_foo'));
+    assert.strictEqual(results[2].tags!.length, 2);
+    assert.isTrue(results[2].tags![0].tooltip!.includes('suite_foo'));
+    assert.isTrue(results[2].tags![1].tooltip!.includes('build_foo'));
 
     assert.isTrue(results[3].summary.includes('test2'));
-    assert.strictEqual(results[3].tags.length, 2);
-    assert.isTrue(results[3].tags[0].tooltip.includes('suite_foo'));
-    assert.isTrue(results[3].tags[1].tooltip.includes('gpu_foo'));
+    assert.strictEqual(results[3].tags!.length, 2);
+    assert.isTrue(results[3].tags![0].tooltip!.includes('suite_foo'));
+    assert.isTrue(results[3].tags![1].tooltip!.includes('gpu_foo'));
   });
 
   test('convertBuildToRun returns correct actions', () => {
-    const startedBuild = {
-      builder: {builder: 'foo'},
+    const startedBuild: Build = {
+      builder: { builder: 'foo' },
       id: '123',
       status: BuildStatus.STARTED,
-      infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
+      infra: { resultdb: { hostname: 'host', invocation: 'inv' } },
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
     const startedRun = fetcher.convertBuildToRun(
-        123456, startedBuild, 1, 1, [{id: '123', status: BuildStatus.STARTED}]);
+        123456, startedBuild, 1, 1, [{id: '123', status: BuildStatus.STARTED, isQuickRun: false, build: startedBuild}], '');
     assert.strictEqual(startedRun.checkName, 'foo');
-    assert.strictEqual(startedRun.actions.length, 1);
-    assert.strictEqual(startedRun.actions[0].name, 'Cancel');
+    assert.strictEqual(startedRun.actions!.length, 1);
+    assert.strictEqual(startedRun.actions![0].name, 'Cancel');
 
-    const noRetryBuild = {
-      builder: {builder: 'foo'},
+    const noRetryBuild: Build = {
+      builder: { builder: 'foo' },
       id: '123456',
       status: BuildStatus.FAILURE,
-      infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
-      tags: [{key: 'skip-retry-in-gerrit', value: 'true'}],
+      infra: { resultdb: { hostname: 'host', invocation: 'inv' } },
+      tags: [{ key: 'skip-retry-in-gerrit', value: 'true' }],
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
     const noRetryRun = fetcher.convertBuildToRun(
-      123456, noRetryBuild, 1, 1, [{id: '123', status: BuildStatus.FAILURE}]);
+      123456, noRetryBuild, 1, 1, [{id: '123', status: BuildStatus.FAILURE, isQuickRun: false, build: noRetryBuild}], '');
     assert.strictEqual(noRetryRun.checkName, 'foo');
-    assert.strictEqual(noRetryRun.actions.length, 0);
+    assert.strictEqual(noRetryRun.actions!.length, 0);
   });
 
   test('convertBuildToRun returns correct build links', () => {
-    const successBuild = {
-      builder: {builder: 'bar', project: 'chromium', bucket: 'baz'},
+    const successBuild: Build = {
+      builder: { builder: 'bar', project: 'chromium', bucket: 'baz' },
       id: '123456',
       summaryMarkdown: 'Build passed',
       number: 987,
       status: BuildStatus.SUCCESS,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
-    const failedBuild = {
-      builder: {builder: 'foo', project: 'chromium', bucket: 'baz'},
+    const failedBuild: Build = {
+      builder: { builder: 'foo', project: 'chromium', bucket: 'baz' },
       id: '123456',
       summaryMarkdown: 'Build failed',
       number: 987,
       status: BuildStatus.FAILURE,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
 
     const successRun = fetcher.convertBuildToRun(
-      123456, successBuild, 1, 1, [{id: '123456', status: BuildStatus.SUCCESS}], []);
+      123456, successBuild, 1, 1, [{id: '123456', status: BuildStatus.SUCCESS, isQuickRun: false, build: successBuild}], '');
     assert.deepEqual(successRun.results, []);
     assert.strictEqual(
       successRun.statusDescription,
@@ -1057,11 +1462,11 @@
     );
 
     const failedRun = fetcher.convertBuildToRun(
-      123456, failedBuild, 1, 1, [{id: '123456', status: BuildStatus.FAILURE}], []);
-    assert.strictEqual(failedRun.results[0].summary, 'Build foo failed.');
-    assert.strictEqual(failedRun.results[0].message, 'Build failed');
+      123456, failedBuild, 1, 1, [{id: '123456', status: BuildStatus.FAILURE, isQuickRun: false, build: failedBuild}], '');
+    assert.strictEqual(failedRun.results![0].summary, 'Build foo failed.');
+    assert.strictEqual(failedRun.results![0].message, 'Build failed');
     assert.deepEqual(
-      failedRun.results[0].links,
+      failedRun.results![0].links,
       [{
         url: 'https://buildbucket.example.com/build/123456',
         tooltip: 'Build',
@@ -1073,15 +1478,18 @@
 
   test('convertVariantsToCheckResults returns correct links', async () => {
     // Build is SUCCESSful and should only contain TestVariant CheckResults.
-    const build = {
-      builder: {builder: 'foo', project: 'bar', bucket: 'baz'},
+    const build: Build = {
+      builder: { builder: 'foo', project: 'bar', bucket: 'baz' },
       id: '123456',
       number: 987,
       status: BuildStatus.SUCCESS,
-      infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
+      infra: { resultdb: { hostname: 'host', invocation: 'inv' } },
+      createTime: '',
+      startTime: '',
+      endTime: ''
     };
 
-    const variants = [{
+    const variants: TestVariant[] = [{
       testMetadata: {name: 'test0'},
       status: TestVariantStatus.UNEXPECTED,
       variantHash: 'deadbeef',
@@ -1095,8 +1503,10 @@
             ],
           },
         },
-        {result: {name: 'this is a bad name'}},
+        {result: {name: 'this is a bad name', tags: []}},
       ],
+      exonerations: [],
+      variant: ''
     }];
 
     const results = await fetcher.convertVariantsToCheckResults(build, variants, 123456);
@@ -1170,44 +1580,47 @@
     const stubbedFetcher =
         new ChecksFetcher(plugin, 'buildbucket.example.com', 2);
     const nonExpResult = await stubbedFetcher.fetch(changeObject);
-    assert.strictEqual(nonExpResult.runs[0].results.length, 1);
-    assert.isFalse(nonExpResult.runs[0].results[0].tags
+    assert.strictEqual(nonExpResult.runs![0].results!.length, 1);
+    assert.isFalse(nonExpResult.runs![0].results![0].tags!
         .map(t => t.name).includes('Experimental'));
     assert.strictEqual(
-          nonExpResult.actions[2].name, 'Show Additional Results');
+          nonExpResult.actions![2].name, 'Show Additional Results');
 
     // Simulate "Show/Hide Additional" click.
-    await nonExpResult.actions[2].callback(1, 1, 1, 1, 'name', 'action');
+    await nonExpResult.actions![2].callback(1);
     sinon.assert.calledOnce(update);
 
     // CheckResult should contain experimental runs.
     const expResult = await stubbedFetcher.fetch(changeObject);
-    assert.strictEqual(expResult.runs.length, 2);
-    assert.isTrue(expResult.runs[0].results[0].tags
+    assert.strictEqual(expResult.runs!.length, 2);
+    assert.isTrue(expResult.runs![0].results![0].tags!
         .map(t => t.name).includes('Experimental'));
-    assert.isFalse(expResult.runs[1].results[0].tags
+    assert.isFalse(expResult.runs![1].results![0].tags!
         .map(t => t.name).includes('Experimental'));
     assert.strictEqual(
-        expResult.actions[2].name, 'Hide Additional Results');
+        expResult.actions![2].name, 'Hide Additional Results');
   });
 
   test('fetch handles ResultDB failures', async () => {
-    const builds = [{
+    const builds: Build[] = [{
       builder: {builder: 'bar'},
       id: '421532',
       infra: {resultdb: {hostname: 'host', invocation: 'inv'}},
       status: BuildStatus.FAILURE,
+      createTime: '',
+      startTime: '',
+      endTime: ''
     }];
     sandbox.stub(ChecksFetcher.prototype, 'fetchDisplayedBuilds')
-        .returns(builds);
+        .returns(Promise.resolve(builds));
     sandbox.stub(ChecksFetcher.prototype, 'fetchTestVariants')
         .returns(Promise.reject('ResultDB failed!'));
 
     // A notice run should be generated.
     const res = await fetcher.fetch(changeObject);
-    assert.strictEqual(res.runs.length, 2);
-    assert.strictEqual(res.runs[1].checkName, 'Notice');
-    assert.strictEqual(res.runs[1].results[0].summary,
+    assert.strictEqual(res.runs!.length, 2);
+    assert.strictEqual(res.runs![1].checkName, 'Notice');
+    assert.strictEqual(res.runs![1].results![0].summary,
         'Test results may be missing.');
   });
 
@@ -1232,7 +1645,7 @@
       ],
     });
 
-    assert.deepEqual(await fetcher.retryFailedBuildsCallback(50, 3, 'repo'), {
+    assert.deepEqual(await fetcher.retryFailedBuildsCallback(50, 3, 'repo', new AbortController()), {
       message: 'No failed builds to retry',
     });
   });
@@ -1288,7 +1701,7 @@
 
     const stub = sandbox.stub(
         ChecksFetcher.prototype, 'scheduleBuildsCallback');
-    await fetcher.retryFailedBuildsCallback(50, 3, 'repo');
+    await fetcher.retryFailedBuildsCallback(50, 3, 'repo', new AbortController());
     sinon.assert.calledWith(
         stub, 50, 3, 'repo', [{ builder: 'bar' }], [RETRY_FAILED_TAG]);
   });
@@ -1296,9 +1709,9 @@
   test('run action returns an error message if failed', async () => {
     const plugin = {
       checks: () => {
-        return {announceUpdate: sandbox.stub()};
+        return { announceUpdate: sandbox.stub() };
       },
-    } as PluginApi;
+    } as unknown as PluginApi;
     const stubbedFetcher =
         new ChecksFetcher(plugin, 'buildbucket.example.com', 2);
     stubbedFetcher.retryEnabled = true;
@@ -1314,6 +1727,9 @@
         builder: {builder: 'foo'} as Builder,
         endTime: '2017-12-15T01:30:15.05Z',
         status: BuildStatus.FAILURE,
+        createTime: '',
+        startTime: '',
+
       },
       'project',
       1,
@@ -1324,16 +1740,16 @@
         } as unknown as Attempt],
     )[0].callback;
 
-    let res = await runAction(123456);
+    let res = await runAction(123456, 0, undefined, undefined, undefined, '');
     assert.strictEqual(
-        res.message, '1 of 1 cancel requests failed. See console logs.');
+        res!.message, '1 of 1 cancel requests failed. See console logs.');
 
     batchStub.onCall(1).returns(Promise.resolve({responses: []}));
     batchStub.onCall(2).returns(
         Promise.resolve({responses: [{error: 'bad schedule'}]}));
-    res = await runAction(123456);
+    res = await runAction(123456, 0, undefined, undefined, undefined, '');
     assert.strictEqual(
-        res.message, '1 of 1 schedule requests failed. See console logs.');
+        res!.message, '1 of 1 schedule requests failed. See console logs.');
   });
 
   test('chooseTryjobsCallback opens a tryjob picker', async () => {
@@ -1347,7 +1763,7 @@
       pluginConfig: {config: 'old'},
       change: {_number: 678, project: 'old/project'},
       revision: {_number: 9},
-    });
+    }) as any;
 
     const picker = {
       close: sandbox.stub(),
@@ -1363,18 +1779,18 @@
     const plugin = {
       popup: sandbox.stub().returns(picker),
       restApi: () => {
-        return {get: sandbox.stub().returns(config)};
+        return { get: sandbox.stub().returns(config) };
       },
       getPluginName: () => 'buildbucket',
       checks: () => {
-        return {announceUpdate: sandbox.stub()};
+        return { announceUpdate: sandbox.stub() };
       },
-    } as PluginApi;
+    } as unknown as PluginApi;
 
     const fetcherWithPlugin = new ChecksFetcher(plugin, 'newhost', 2);
     const result = await fetcherWithPlugin.fetch(changeObject);
-    assert.strictEqual(result.actions[0].name, 'Choose Tryjobs');
-    await result.actions[0].callback(123456, 321);
+    assert.strictEqual(result.actions![0].name, 'Choose Tryjobs');
+    await result.actions![0].callback(123456);
 
     sinon.assert.calledOnce(plugin.popup as any);
     assert.strictEqual(element.buildbucketHost, 'newhost');
@@ -1388,17 +1804,17 @@
     stubFetch({responses: [{searchBuilds: {}}]});
     const plugin = {
       restApi: () => {
-        return {get: sandbox.stub().returns({buckets: []})};
+        return { get: sandbox.stub().returns({ buckets: [] }) };
       },
       getPluginName: () => 'buildbucket',
       checks: () => {
-        return {announceUpdate: sandbox.stub()};
+        return { announceUpdate: sandbox.stub() };
       },
-    } as PluginApi;
+    } as unknown as PluginApi;
 
     const fetcherWithPlugin = new ChecksFetcher(plugin, 'newhost', 2);
     const result = await fetcherWithPlugin.fetch(changeObject);
-    assert.isFalse(result.actions.some(a => a.name === 'Choose Tryjobs'));
+    assert.isFalse(result.actions!.some(a => a.name === 'Choose Tryjobs'));
   });
 
   test('retry failed is hidden if disabled in config', async () => {
@@ -1417,18 +1833,18 @@
     });
     const plugin = {
       restApi: () => {
-        return {get: sandbox.stub().returns({hideRetryButton: true})};
+        return { get: sandbox.stub().returns({ hideRetryButton: true }) };
       },
       getPluginName: () => 'buildbucket',
       checks: () => {
-        return {announceUpdate: sandbox.stub()};
+        return { announceUpdate: sandbox.stub() };
       },
-    } as PluginApi;
+    } as unknown as PluginApi;
 
     const fetcherWithPlugin = new ChecksFetcher(plugin, 'newhost', 2);
     const result = await fetcherWithPlugin.fetch(changeObject);
-    assert.isFalse(result.actions.some(a => a.name === 'Retry Failed Builds'));
-    assert.strictEqual(result.runs[0].actions.length, 0);
+    assert.isFalse(result.actions!.some(a => a.name === 'Retry Failed Builds'));
+    assert.strictEqual(result.runs![0].actions!.length, 0);
   });
 
   test('show additional results and give feedback actions can be primary',
@@ -1436,25 +1852,27 @@
     stubFetch({responses: [{searchBuilds: {}}]});
     const plugin = {
       restApi: () => {
-        return {get: sandbox.stub().returns({
-          buckets: [],
-          hideRetryButton: true,
-        })};
+        return {
+          get: sandbox.stub().returns({
+            buckets: [],
+            hideRetryButton: true,
+          })
+        };
       },
       getPluginName: () => 'buildbucket',
       checks: () => {
-        return {announceUpdate: sandbox.stub()};
+        return { announceUpdate: sandbox.stub() };
       },
-    } as PluginApi;
+    } as unknown as PluginApi;
 
     const fetcherWithPlugin = new ChecksFetcher(plugin, 'newhost', 2);
     const result = await fetcherWithPlugin.fetch(changeObject);
-    assert.strictEqual(result.actions[0].name, 'Show Additional Results');
-    assert.isTrue(result.actions[0].primary);
-    assert.strictEqual(result.actions[1].name, 'Show Previous Attempts');
-    assert.isFalse(result.actions[1].primary);
-    assert.strictEqual(result.actions[2].name, 'Give Feedback');
-    assert.isTrue(result.actions[2].primary);
+    assert.strictEqual(result.actions![0].name, 'Show Additional Results');
+    assert.isTrue(result.actions![0].primary);
+    assert.strictEqual(result.actions![1].name, 'Show Previous Attempts');
+    assert.isFalse(result.actions![1].primary);
+    assert.strictEqual(result.actions![2].name, 'Give Feedback');
+    assert.isTrue(result.actions![2].primary);
   });
 
   test('fetch aborts pending fetch requests', async () => {
@@ -1462,7 +1880,7 @@
       new Promise((resolve, _) => {
         const wait = setTimeout(() => {
           clearTimeout(wait);
-          resolve('foo');
+          resolve('foo' as unknown as Response);
         }, 9999999);
       })
     );
@@ -1473,19 +1891,19 @@
     fetcher.fetch(changeObject);
     await new Promise(r => setTimeout(r, 100));
     const firstController = fetcher.controllers.get(controllerKey);
-    assert.strictEqual(firstController.signal.aborted, false);
+    assert.strictEqual(firstController!.signal.aborted, false);
 
     // Following fetches should be aborted.
     fetcher.fetch(changeObject);
     await new Promise(r => setTimeout(r, 100));
     const secondController = fetcher.controllers.get(controllerKey);
     assert.notStrictEqual(firstController, secondController);
-    assert.strictEqual(firstController.signal.aborted, true);
-    assert.strictEqual(secondController.signal.aborted, false);
+    assert.strictEqual(firstController!.signal.aborted, true);
+    assert.strictEqual(secondController!.signal.aborted, false);
 
     fetcher.fetch(changeObject)
     await new Promise(r => setTimeout(r, 100));
-    assert.strictEqual(secondController.signal.aborted, true);
+    assert.strictEqual(secondController!.signal.aborted, true);
   });
 
 test('fetch aborts based on repo, change, and patchset', async () => {
@@ -1493,29 +1911,71 @@
       new Promise((resolve, _) => {
         const wait = setTimeout(() => {
           clearTimeout(wait);
-          resolve('foo');
+          resolve('foo' as unknown as Response);
         }, 9999999);
       })
     );
 
-    const firstChangeObject = {
+    const firstChangeObject: ChangeData = {
       changeNumber: 123456,
       patchsetNumber: 1,
       repo: 'foo/bar',
       changeInfo: {
-        status: 'NEW',
-        revisions: [{kind: 'REWORK', _number: 1}],
+        status: ChangeStatus.NEW,
+        revisions: {
+          1: {
+            kind: RevisionKind.REWORK,
+            _number: 1 as PatchSetNum,
+            created: '' as Timestamp,
+            uploader: '' as AccountInfo,
+            ref: '' as GitRef,
+          },
+        },
+        id: '' as ChangeInfoId,
+        project: '' as RepoName,
+        branch: '' as BranchName,
+        change_id: '' as ChangeId,
+        subject: '',
+        created: '' as Timestamp,
+        updated: '' as Timestamp,
+        insertions: 0,
+        deletions: 0,
+        _number: 0 as NumericChangeId,
+        owner: '' as AccountInfo,
+        reviewers: {},
       },
+      patchsetSha: ''
     };
 
-    const secondChangeObject = {
+    const secondChangeObject: ChangeData = {
       changeNumber: 123456,
       patchsetNumber: 2,
       repo: 'foo/bar',
       changeInfo: {
-        status: 'NEW',
-        revisions: [{kind: 'REWORK', _number: 1}],
+        status: ChangeStatus.NEW,
+        revisions: {
+          1: {
+            kind: RevisionKind.REWORK,
+            _number: 1 as PatchSetNum,
+            created: '' as Timestamp,
+            uploader: '' as AccountInfo,
+            ref: '' as GitRef,
+          },
+        },
+        id: '' as ChangeInfoId,
+        project: '' as RepoName,
+        branch: '' as BranchName,
+        change_id: '' as ChangeId,
+        subject: '',
+        created: '' as Timestamp,
+        updated: '' as Timestamp,
+        insertions: 0,
+        deletions: 0,
+        _number: 0 as NumericChangeId,
+        owner: '' as AccountInfo,
+        reviewers: {},
       },
+      patchsetSha: ''
     };
 
     // Fetch from firstChangeObject.
@@ -1524,7 +1984,7 @@
     fetcher.fetch(firstChangeObject);
     await new Promise(r => setTimeout(r, 100));
     const firstController = fetcher.controllers.get(firstControllerKey);
-    assert.strictEqual(firstController.signal.aborted, false);
+    assert.strictEqual(firstController!.signal.aborted, false);
 
     // A fetch from secondChangeObject should not abort firstController.
     fetcher.fetch(secondChangeObject);
@@ -1532,13 +1992,13 @@
     const secondControllerKey = fetcher.getChangeDataId(secondChangeObject);
     const secondController = fetcher.controllers.get(secondControllerKey);
     assert.notStrictEqual(firstController, secondController);
-    assert.strictEqual(firstController.signal.aborted, false);
-    assert.strictEqual(secondController.signal.aborted, false);
+    assert.strictEqual(firstController!.signal.aborted, false);
+    assert.strictEqual(secondController!.signal.aborted, false);
 
     // Aborting secondChangeObject should not abort firstController.
     fetcher.fetch(secondChangeObject)
     await new Promise(r => setTimeout(r, 100));
-    assert.strictEqual(firstController.signal.aborted, false);
-    assert.strictEqual(secondController.signal.aborted, true);
+    assert.strictEqual(firstController!.signal.aborted, false);
+    assert.strictEqual(secondController!.signal.aborted, true);
   });
 });
diff --git a/web/test/checks-result_test.ts b/web/test/checks-result_test.ts
index ea1fddf..bd38d29 100644
--- a/web/test/checks-result_test.ts
+++ b/web/test/checks-result_test.ts
@@ -11,12 +11,12 @@
     parseMarkdown,
   } from '../checks-result';
 
-function sanitizedAttr(attr) {
+function sanitizedAttr(attr: string) {
   return `rel="noopener" target="_blank" ${attr}`;
 }
 
 suite('checks-result tests', () => {
-  let sandbox;
+  let sandbox: sinon.SinonSandbox;
   const defaultMetaAttr = 'alt="chromeChecksMetadataTagDefault"';
   const greenMetaAttr = 'alt="chromeChecksMetadataTagGreen"';
   const redMetaAttr = 'alt="chromeChecksMetadataTagRed"';
@@ -24,7 +24,7 @@
   setup(() => {
     sandbox = sinon.createSandbox();
     sandbox.stub(ResultDbV1Client.prototype, 'getAuthorizationHeader')
-        .returns({'authorization': 'accessToken'});
+        .returns(Promise.resolve({'authorization': 'accessToken'}));
   });
 
   teardown(() => {
@@ -62,7 +62,7 @@
     await installChecksResult(element);
 
     // Check element is styled.
-    const style = (element.getRootNode() as HTMLElement).querySelector('style').textContent;
+    const style = (element.getRootNode() as any).querySelector('style').textContent;
     assert.isTrue(style.includes('word-break: break-all;'));
     assert.isTrue(style.includes('white-space: pre-wrap;'));
 
@@ -74,7 +74,7 @@
     fetchStub.returns(Promise.resolve({
       ok: true,
       text: async () => 'foo test failed!',
-    }));
+    } as Response));
 
     const artifactStub = sandbox.stub(
         ResultDbV1Client.prototype, 'listArtifacts');
@@ -143,6 +143,7 @@
           artifactId: 'baz',
           fetchUrl: 'https://baz.com',
           fetchUrlExpiration: '2017-12-15T01:30:15.05Z',
+          sizeBytes: 0,
         },
       ],
     }));
@@ -157,7 +158,7 @@
       }],
       plugin: {
         checks: () => ({
-          updateResult: (_1, _2) => {},
+          updateResult: (_1: any, _2: any) => {},
         }),
       },
       rdbHost: 'host',
@@ -191,7 +192,7 @@
       ],
       plugin: {
         checks: () => ({
-          updateResult: (_1, _2) => {},
+          updateResult: (_1: any, _2: any) => {},
         }),
       },
       rdbHost: 'host',
@@ -218,18 +219,18 @@
     fetchStub.withArgs('https://foo.com/?n=50000').returns(Promise.resolve({
       ok: true,
       text: async () => 'foo test failed!',
-    }));
+    } as Response));
     fetchStub.withArgs('https://bar.org/?n=50000').returns(Promise.resolve({
       ok: true,
       text: async () => 'bar test passed!',
-    }));
+    } as Response));
     fetchStub.withArgs('https://baz.org/?n=50000').returns(Promise.reject(
         new Error('bad fetch')));
     fetchStub.withArgs('https://boo.org/?n=50000').returns(Promise.resolve({
       ok: false,
       status: 500,
       text: async () => 'server error',
-    }));
+    } as Response));
 
     const resArtifacts = [
       {
@@ -281,7 +282,7 @@
     fetchStub.onCall(1).returns(Promise.resolve({
       ok: true,
       text: async () => 'foobar test failed!',
-    }));
+    } as Response));
     fetchStub.onCall(2).returns(Promise.reject(
         new Error('bad fetch')));
 
@@ -295,23 +296,23 @@
     // Replace and fetch.
     const html = '<p><text-artifact artifact-id="foobar" /></p>';
     assert.strictEqual(
-      await replaceTextArtifacts(html, artifacts, null),
+      await replaceTextArtifacts(html, artifacts, []),
       `<p><a ${defaultMetaAttr}>Failed to fetch foobar artifact: bad fetch. See console logs.</a></p>`,
     );
 
     assert.strictEqual(
-      await replaceTextArtifacts(html, artifacts, null),
+      await replaceTextArtifacts(html, artifacts, []),
       '<p>foobar test failed!</p>',
     );
     sinon.assert.calledTwice((fetch as any));
 
     // Check that fetches after the first successful fetch use cache.
     assert.strictEqual(
-      await replaceTextArtifacts(html, artifacts, null),
+      await replaceTextArtifacts(html, artifacts, []),
       '<p>foobar test failed!</p>',
     );
     assert.strictEqual(
-      await replaceTextArtifacts(html, artifacts, null),
+      await replaceTextArtifacts(html, artifacts, []),
       '<p>foobar test failed!</p>',
     );
     sinon.assert.calledTwice((fetch as any));
@@ -323,12 +324,12 @@
     fetchStub.withArgs('https://a1.com/?n=50000').returns(Promise.resolve({
       ok: true,
       text: async () => artifactOne,
-    }));
+    } as Response));
     const artifactTwo = 'a'.repeat(50002);
     fetchStub.withArgs('https://a2.com/?n=50000').returns(Promise.resolve({
       ok: true,
       text: async () => artifactTwo,
-    }));
+    } as Response));
 
     const artifacts = [
       {
@@ -347,14 +348,14 @@
 
     assert.strictEqual(
       await replaceTextArtifacts(
-          '<p><text-artifact artifact-id="a1"/></p>', artifacts, null),
+          '<p><text-artifact artifact-id="a1"/></p>', artifacts, []),
       `<p>${artifactOne}<a ${defaultMetaAttr}>\n\n...1 more byte...\n` +
           'More information in a1 artifact</a></p>',
     );
 
     assert.strictEqual(
       await replaceTextArtifacts(
-          '<p><text-artifact artifact-id="a2"/></p>', artifacts, null),
+          '<p><text-artifact artifact-id="a2"/></p>', artifacts, []),
       `<p>${artifactTwo}<a ${defaultMetaAttr}>\n\n...2 more bytes...\n` +
           'More information in a2 artifact</a></p>',
     );
diff --git a/web/test/cr-tryjob-picker_test.ts b/web/test/cr-tryjob-picker_test.ts
index 76b10cf..ad362cd 100644
--- a/web/test/cr-tryjob-picker_test.ts
+++ b/web/test/cr-tryjob-picker_test.ts
@@ -5,7 +5,7 @@
 import {CrTryjobPicker} from '../cr-tryjob-picker';
 
 suite('cr-tryjob-picker basic tests', () => {
-  let sandbox;
+  let sandbox: sinon.SinonSandbox;
   let element: CrTryjobPicker;
   let closeFired = false;
 
@@ -62,19 +62,27 @@
   test('bucket filter function filters builders', () => {
     let fn = element.computeBucketFilterFn(_ => true);
     assert.isTrue(fn({
-      builders: ['blerp', 'GERP'],
-    }));
+    builders: ['blerp', 'GERP'],
+    project: '',
+    bucket: ''
+}));
 
     fn = element.computeBucketFilterFn(s => /gerp/.test(s));
     assert.isTrue(fn({
-      builders: ['blerp', 'gerp'],
-    }));
+    builders: ['blerp', 'gerp'],
+    project: '',
+    bucket: ''
+}));
     assert.isTrue(fn({
-      builders: ['gerp'],
-    }));
+    builders: ['gerp'],
+    project: '',
+    bucket: ''
+}));
     assert.isFalse(fn({
-      builders: ['blerp'],
-    }));
+    builders: ['blerp'],
+    project: '',
+    bucket: ''
+}));
   });
 
   test('filter compilation with literal string', () => {
@@ -109,7 +117,7 @@
 
   test('selectedBuilders updated on checkbox change', async () => {
     await element.bucketsUpdating;
-    const addButton = element.shadowRoot.querySelector('gr-button[primary]');
+    const addButton = element.shadowRoot?.querySelector('gr-button[primary]');
     const el = document.createElement('input');
     el.type = 'checkbox';
     el.setAttribute('data-project', 'proj.foo');
@@ -130,7 +138,7 @@
     assert.equal(Object.keys(element.selectedBuilders).length, 1);
     assert.isTrue(
       element.computeChecked('proj.foo', 'bucket.bar', 'builder.baz'));
-    assert.equal(addButton.getAttribute("disabled"), "");
+    assert.equal(addButton?.getAttribute("disabled"), "");
   });
 
   test('computeBuckets merges builders', async () => {
@@ -199,22 +207,22 @@
       }],
     };
     await element.bucketsUpdating;
-    const addButton = element.shadowRoot.querySelector('gr-button[primary]');
-    assert.isTrue(addButton.hasAttribute('disabled'));
-    const checkbox = element.shadowRoot.querySelector('input[type="checkbox"]');
+    const addButton = element.shadowRoot?.querySelector('gr-button[primary]');
+    assert.isTrue(addButton?.hasAttribute('disabled'));
+    const checkbox = element.shadowRoot?.querySelector('input[type="checkbox"]');
     assert.ok(checkbox);
     const changePromise = new Promise((resolve) => {
       element.addEventListener('change', resolve);
     });
     (checkbox as any).checked = true;
-    checkbox.dispatchEvent(
+    checkbox?.dispatchEvent(
       new CustomEvent('change', {bubbles: true, composed: true}));
     await changePromise;
-    assert.isFalse(addButton.hasAttribute('disabled'));
+    assert.isFalse(addButton?.hasAttribute('disabled'));
   });
 
   test('includeTrybots updated when bots selected', async () => {
-    let fn = (project, bucket, builder, isChecked) => {
+    let fn = (project: string, bucket: string, builder: string, isChecked: boolean) => {
       const el = document.createElement('input');
       el.type = 'checkbox';
       el.setAttribute('data-project', project);
@@ -266,12 +274,12 @@
         builder: 'linux-rel',
       },
     };
-    sandbox.stub(BuildbucketV2Client.prototype, 'batch').returns({});
+    sandbox.stub(BuildbucketV2Client.prototype, 'batch').returns(Promise.resolve({}));
     await element.onAddTap();
     assert.isTrue(closeFired);
     assert.isFalse(element.disabled);
     assert.deepEqual(element.selectedBuilders, {});
-    assert.isNull(element.clientOperationId);
+    assert.isUndefined(element.clientOperationId);
   });
 
   test('on HTTP request error', async () => {
diff --git a/web/test/lru-cache-object_test.ts b/web/test/lru-cache-object_test.ts
index 67fd038..a2fc571 100644
--- a/web/test/lru-cache-object_test.ts
+++ b/web/test/lru-cache-object_test.ts
@@ -9,7 +9,7 @@
   } from '../lru-storage-cache-object';
 
 suite('LRUMemoryCacheObject', () => {
-  let cache;
+  let cache: LRUMemoryCacheObject;
 
   setup(() => {
     cache = new LRUMemoryCacheObject(2);
@@ -65,8 +65,8 @@
 });
 
 suite('LRUStorageCacheObject', () => {
-  let sandbox;
-  let cache;
+  let sandbox: sinon.SinonSandbox;
+  let cache: LRUStorageCacheObject;
   const cacheKey = 'key';
 
   setup(() => {
@@ -90,11 +90,11 @@
   test('write then clear then read yields empty cache', () => {
     cache.write('a', 1);
     const key = cache.formatKey('a');
-    assert.deepEqual(JSON.parse(localStorage.getItem(key)), {value: 1});
+    assert.deepEqual(JSON.parse(localStorage.getItem(key)!), {value: 1});
     cache.clear();
     assert.strictEqual(cache.read('a'), undefined);
     assert.deepEqual(cache.accessKeys, []);
-    assert.strictEqual(JSON.parse(localStorage.getItem(key)), null);
+    assert.strictEqual(JSON.parse(localStorage.getItem(key)!), null);
   });
 
   test('delete deletes keys', () => {
@@ -140,7 +140,7 @@
     cache.write('b', 2);
     const smallerCache = new LRUStorageCacheObject(1, cacheKey);
     assert.strictEqual(smallerCache.read('a'), undefined);
-    assert.strictEqual(JSON.parse(localStorage.getItem('a')), null);
+    assert.strictEqual(JSON.parse(localStorage.getItem('a')!), null);
     assert.strictEqual(smallerCache.read('b'), 2);
     smallerCache.clear();
 
diff --git a/web/test/resultdb-client_test.ts b/web/test/resultdb-client_test.ts
index 8cad9c0..aec1c69 100644
--- a/web/test/resultdb-client_test.ts
+++ b/web/test/resultdb-client_test.ts
@@ -1,14 +1,17 @@
+import { BuildStatus } from '../buildbucket-client';
+import { Build } from '../checks-fetcher';
 import {
     ResultDbV1Client,
+    TestVariantStatus,
   } from '../resultdb-client';
 suite('ResultDbV1Client tests', () => {
-  let sandbox;
-  let client;
+  let sandbox: sinon.SinonSandbox;
+  let client: ResultDbV1Client;
 
   setup(() => {
     sandbox = sinon.createSandbox();
     client = new ResultDbV1Client('resultdb.example.com', '123456');
-    client.getAuthorizationHeader = async () => ({'authroization': 'access_token'});
+    client.getAuthorizationHeader = async () => ({'authorization': 'access_token'});
   });
 
   teardown(() => {
@@ -17,24 +20,29 @@
 
   test('fetchTestVariants caches both additional and non-additional variants',
       async () => {
-    const build = {
+    const build: Build = {
       id: '123',
       endTime: '2017-12-15T01:30:15.05Z',
       infra: {
         resultdb: {
+          hostname: '',
           invocation: 'foo'
         }
       },
+      builder: {},
+      status: BuildStatus.SCHEDULED,
+      createTime: '',
+      startTime: ''
     };
     const fetch = sandbox.stub(window, 'fetch');
     fetch.onCall(0).returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify({testVariants: []})}`,
-    }));
+    } as Response));
     fetch.onCall(1).returns(Promise.resolve({
       ok: true,
-      text: async () => `)]}'${JSON.stringify({testVariants: [{status: 'FLAKY'}]})}`,
-    }));
+      text: async () => `)]}'${JSON.stringify({testVariants: [{status: 'FLAKY', variant: '', results: [], exonerations: []}]})}`,
+    } as Response));
 
     // inclAdditional is false.
     assert.deepEqual(
@@ -45,7 +53,7 @@
     // inclAdditional is true.
     assert.deepEqual(
       await client.fetchTestVariants(build, 1, true),
-      [{status: 'FLAKY'}],
+      [{status: TestVariantStatus.FLAKY, variant: '', results: [], exonerations: []}],
     );
     sinon.assert.calledTwice(fetch);
 
@@ -57,7 +65,7 @@
     );
     assert.deepEqual(
       await client.fetchTestVariants(build, 1, true),
-      [{status: 'FLAKY'}],
+      [{status: TestVariantStatus.FLAKY, variant: '', results: [], exonerations: []}],
     );
     sinon.assert.calledTwice(fetch);
   });
@@ -68,12 +76,18 @@
           () => ({
             name: 'invocations/123/result/artifacts/foo',
             fetchUrlExpiration: '2017-12-15T01:30:15.05Z',
+            artifactId: '',
+            fetchUrl: '',
+            sizeBytes: 0,
           })),
     };
     const secondPage = {
       artifacts: [{
         name: 'invocations/123/artifacts/foo',
         fetchUrlExpiration: '2017-12-15T01:30:15.05Z',
+        artifactId: '',
+        fetchUrl: '',
+        sizeBytes: 0,
       }],
     };
 
@@ -81,11 +95,11 @@
     fetch.onCall(0).returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify(firstPage)}`,
-    }));
+    } as Response));
     fetch.onCall(1).returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify(secondPage)}`,
-    }));
+    } as Response));
 
     assert.deepEqual(
       await client.fetchArtifacts('invocations/123/result', true),
@@ -102,29 +116,35 @@
     const fooArtifacts = [{
       name: 'invocations/123/artifacts/foo',
       fetchUrlExpiration: '2999-12-15T01:30:15.05Z',
+      artifactId: '',
+      fetchUrl: '',
+      sizeBytes: 0,
     }];
     const barArtifacts = [{
       name: 'invocations/123/artifacts/bar',
       fetchUrlExpiration: '2999-12-15T01:30:15.05Z',
+      artifactId: '',
+      fetchUrl: '',
+      sizeBytes: 0,
     }];
 
     const fetch = sandbox.stub(window, 'fetch');
     fetch.onCall(0).returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify({artifacts: fooArtifacts})}`,
-    }));
+    } as Response));
     fetch.onCall(1).returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify([])}`,
-    }));
+    } as Response));
     fetch.onCall(2).returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify({artifacts: barArtifacts})}`,
-    }));
+    } as Response));
     fetch.onCall(3).returns(Promise.resolve({
       ok: true,
       text: async () => `)]}'${JSON.stringify([])}`,
-    }));
+    } as Response));
 
     assert.deepEqual(await client.fetchArtifacts('invocations/123/foo', true),
       {
diff --git a/web/test/test-util.ts b/web/test/test-util.ts
index 81a9c3e..6e8ec0d 100644
--- a/web/test/test-util.ts
+++ b/web/test/test-util.ts
@@ -10,16 +10,16 @@
  *   If RegExp or string, then rejection reason is expected to have message
  *   property (like Error) that matches the expected reason.
  */
-export async function assertRejects(promise, expectedReason = null) {
+export async function assertRejects(promise: any, expectedReason: any = null) {
   try {
     await promise;
     assert.fail('exception', 'no exception');
-  } catch (actualReason) {
+  } catch (actualReason: unknown) {
     if (expectedReason) {
       if (expectedReason instanceof RegExp) {
-        assert.match(actualReason.message, expectedReason);
+        assert.match((actualReason as Error).message, expectedReason);
       } else if (typeof expectedReason == 'string') {
-        assert.equal(actualReason.message, expectedReason);
+        assert.equal((actualReason as Error).message, expectedReason);
       } else {
         assert.deepEqual(actualReason, expectedReason);
       }
@@ -27,7 +27,7 @@
   }
 }
 
-export function deepFreeze(obj) {
+export function deepFreeze(obj: any) {
   for (const p of Object.keys(obj)) {
     const v = obj[p];
     if (typeof v == 'object' && v !== null) {
diff --git a/web/tsconfig.json b/web/tsconfig.json
index f1f2447..05cd7ec 100644
--- a/web/tsconfig.json
+++ b/web/tsconfig.json
@@ -3,9 +3,6 @@
   "compilerOptions": {
     /* outDir for IDE (overridden by Bazel rule arg) */
     "outDir": "../../../.ts-out/plugins/buildbucket/web",
-    "noImplicitAny": false,
-    "strictNullChecks": false,
-    "strictPropertyInitialization": false,
     "lib": ["dom", "dom.iterable", "es2021"]
   },
   "include": [