Skip to content

Take and populate screenshot #114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
2 changes: 2 additions & 0 deletions packages/synthetics-sdk-broken-links/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
"@types/chai": "^4.3.4",
"@types/express": "^4.17.17",
"@types/node": "^18.15.10",
"@types/proxyquire": "^1.3.31",
"@types/sinon": "^10.0.16",
"@types/supertest": "^2.0.12",
"chai": "^4.3.7",
"chai-exclude": "^2.1.0",
"express": "^4.18.2",
"proxyquire": "^2.1.3",
"node-mocks-http": "^1.13.0",
"sinon": "^16.1.1",
"supertest": "^6.3.3",
"synthetics-sdk-broken-links": "file:./"
Expand Down
48 changes: 33 additions & 15 deletions packages/synthetics-sdk-broken-links/src/broken_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { processOptions } from './options_func';
import {
createStorageClientIfStorageSelected,
getOrCreateStorageBucket,
StorageParameters,
} from './storage_func';

// External Dependencies
Expand Down Expand Up @@ -100,7 +101,11 @@ try {
instantiateMetadata(synthetics_sdk_broken_links_package);

export async function runBrokenLinks(
inputOptions: BrokenLinkCheckerOptions
inputOptions: BrokenLinkCheckerOptions,
args: {
executionId: string | undefined;
checkId: string | undefined;
}
): Promise<SyntheticResult> {
// init
const startTime = new Date().toISOString();
Expand All @@ -112,6 +117,11 @@ export async function runBrokenLinks(
const adjusted_synthetic_timeout_millis =
options.total_synthetic_timeout_millis! - 7000;

// Create Promise and variables used to set and resolve the time limit
// imposed by `adjusted_synthetic_timeout`
const [timeLimitPromise, timeLimitTimeout, timeLimitresolver] =
getTimeLimitPromise(startTime, adjusted_synthetic_timeout_millis);

const errors: BaseError[] = [];

// Initialize Storage Client with Error Handling. Set to `null` if
Expand All @@ -121,18 +131,21 @@ export async function runBrokenLinks(
options.screenshot_options!.capture_condition
);

// TODO. Just to show where this will be called. uncommented in next PR
// Bucket Validation
// const bucket: Bucket | null = await getOrCreateStorageBucket(
// storageClient,
// options.screenshot_options!.storage_location,
// errors
// );
// // Bucket Validation
const bucket: Bucket | null = await getOrCreateStorageBucket(
storageClient,
options.screenshot_options!.storage_location,
errors
);

// Create Promise and variables used to set and resolve the time limit
// imposed by `adjusted_synthetic_timeout`
const [timeLimitPromise, timeLimitTimeout, timeLimitresolver] =
getTimeLimitPromise(startTime, adjusted_synthetic_timeout_millis);
// TODO
const storageParams: StorageParameters = {
storageClient: storageClient,
bucket: bucket,
checkId: args.checkId || '_',
executionId: args.executionId || '_',
screenshotNumber: 1,
};

const followed_links: BrokenLinksResultV1_SyntheticLinkResult[] = [];

Expand All @@ -147,7 +160,8 @@ export async function runBrokenLinks(
originPage,
options,
startTime,
adjusted_synthetic_timeout_millis
adjusted_synthetic_timeout_millis,
storageParams
)
);

Expand All @@ -169,7 +183,8 @@ export async function runBrokenLinks(
linksToFollow,
options,
startTime,
adjusted_synthetic_timeout_millis
adjusted_synthetic_timeout_millis,
storageParams
))
);
return true;
Expand All @@ -188,6 +203,7 @@ export async function runBrokenLinks(
runtime_metadata,
options,
followed_links,
storageParams,
errors
);
} catch (err) {
Expand Down Expand Up @@ -215,7 +231,8 @@ async function checkOriginLink(
originPage: Page,
options: BrokenLinksResultV1_BrokenLinkCheckerOptions,
startTime: string,
adjusted_synthetic_timeout_millis: number
adjusted_synthetic_timeout_millis: number,
storageParams: StorageParameters
): Promise<BrokenLinksResultV1_SyntheticLinkResult> {
let originLinkResult: BrokenLinksResultV1_SyntheticLinkResult;

Expand All @@ -232,6 +249,7 @@ async function checkOriginLink(
originPage,
{ target_uri: options.origin_uri, anchor_text: '', html_element: '' },
options,
storageParams,
true
);

Expand Down
10 changes: 9 additions & 1 deletion packages/synthetics-sdk-broken-links/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import { Request, Response } from 'express';
// Internal Project Files
import { runBrokenLinks, BrokenLinkCheckerOptions } from './broken_links';

const syntheticExecutionIdHeader = 'Synthetic-Execution-Id';
const checkIdHeader = 'Check-Id';

/**
* Middleware for easy invocation of SyntheticSDK broken links, and may be used to
* register a GoogleCloudFunction http function, or express js compatible handler.
Expand All @@ -29,5 +32,10 @@ import { runBrokenLinks, BrokenLinkCheckerOptions } from './broken_links';
export function runBrokenLinksHandler(options: BrokenLinkCheckerOptions) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return async (req: Request, res: Response): Promise<any> =>
res.send(await runBrokenLinks(options));
res.send(
await runBrokenLinks(options, {
executionId: req.get(syntheticExecutionIdHeader),
checkId: req.get(checkIdHeader),
})
);
}
65 changes: 64 additions & 1 deletion packages/synthetics-sdk-broken-links/src/link_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Standard Libraries
import * as path from 'path';

// Internal Project Files
import {
BaseError,
BrokenLinksResultV1,
BrokenLinksResultV1_BrokenLinkCheckerOptions,
BrokenLinksResultV1_BrokenLinkCheckerOptions_LinkOrder,
BrokenLinksResultV1_BrokenLinkCheckerOptions_ScreenshotOptions_CaptureCondition as ApiCaptureCondition,
BrokenLinksResultV1_SyntheticLinkResult,
GenericResultV1,
getRuntimeMetadata,
Expand All @@ -28,6 +32,7 @@ import {

// External Dependencies
import { HTTPResponse } from 'puppeteer';
import { StorageParameters } from './storage_func';

/**
* Represents an intermediate link with its properties.
Expand Down Expand Up @@ -157,7 +162,7 @@ function parseFollowedLinks(
options: {} as BrokenLinksResultV1_BrokenLinkCheckerOptions,
origin_link_result: {} as BrokenLinksResultV1_SyntheticLinkResult,
followed_link_results: [],
execution_data_storage_path: '', // TODO: make sure that when this is set it begins with gs://
execution_data_storage_path: '',
errors: [],
};

Expand Down Expand Up @@ -223,13 +228,20 @@ export function createSyntheticResult(
runtime_metadata: { [key: string]: string },
options: BrokenLinksResultV1_BrokenLinkCheckerOptions,
followed_links: BrokenLinksResultV1_SyntheticLinkResult[],
storageParams: StorageParameters,
errors: BaseError[]
): SyntheticResult {
// Create BrokenLinksResultV1 by parsing followed links and setting options
const broken_links_result: BrokenLinksResultV1 =
parseFollowedLinks(followed_links);
broken_links_result.options = options;
broken_links_result.errors = errors;
broken_links_result.execution_data_storage_path = storageParams.bucket
? 'gs://' +
storageParams.bucket.name +
'/' +
getStoragePathToExecution(storageParams, options)
: '';

// Create SyntheticResult object
const synthetic_result: SyntheticResult = {
Expand Down Expand Up @@ -273,6 +285,26 @@ export function shuffleAndTruncate(
}

/**
* Determines whether a screenshot should be taken based on screenshot options and link result.
*
* @param options - BrokenLinksResultV1_BrokenLinkCheckerOptions
* @param passed - boolean indicating whether the link navigation succeeded
* @returns true if a screenshot should be taken, false otherwise
*/
export function shouldTakeScreenshot(
options: BrokenLinksResultV1_BrokenLinkCheckerOptions,
passed: boolean
): boolean {
return (
options.screenshot_options!.capture_condition === ApiCaptureCondition.ALL ||
(options.screenshot_options!.capture_condition ===
ApiCaptureCondition.FAILING &&
!passed)
);
}

/**

* Sanitizes an object name string for safe use, ensuring compliance with
* naming restrictions.
*
Expand Down Expand Up @@ -303,6 +335,37 @@ export function sanitizeObjectName(
.replace(/\s+/g, '_'); // Replace one or more spaces with underscores
}

export function getStoragePathToExecution(
storageParams: StorageParameters,
options: BrokenLinksResultV1_BrokenLinkCheckerOptions
) {
try {
const storageLocation = options.screenshot_options!.storage_location;
let writeDestination = '';

// extract folder name for a given storage location. If there is no '/'
// present then the storageLocation is just a folder
const firstSlashIndex = storageLocation.indexOf('/');
if (firstSlashIndex !== -1)
writeDestination = storageLocation.substring(firstSlashIndex + 1);

// Ensure writeDestination ends with a slash for proper path joining
if (writeDestination && !writeDestination.endsWith('/')) {
writeDestination += '/';
}

writeDestination = path.join(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want a slash between checkId and executionId?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this question was getting at - if the path.join function adds a slash by are we doing it manually for write destination above?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I see, yes I think that may be redundant

writeDestination,
storageParams.checkId,
storageParams.executionId
);

return writeDestination;
} catch (err) {
return '';
}
}

export function getTimeLimitPromise(
startTime: string,
totalTimeoutMillis: number,
Expand Down
Loading