<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: QAProEngineer</title>
    <description>The latest articles on DEV Community by QAProEngineer (@qaproengineer).</description>
    <link>https://dev.to/qaproengineer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F455909%2Fea4db7e0-d22d-46cd-b53f-439c1a286de4.jpeg</url>
      <title>DEV Community: QAProEngineer</title>
      <link>https://dev.to/qaproengineer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/qaproengineer"/>
    <language>en</language>
    <item>
      <title>Handling Dynamic Preview URLs in Cypress Tests</title>
      <dc:creator>QAProEngineer</dc:creator>
      <pubDate>Thu, 02 Jan 2025 21:55:36 +0000</pubDate>
      <link>https://dev.to/qaproengineer/handling-dynamic-preview-urls-in-cypress-tests-2k91</link>
      <guid>https://dev.to/qaproengineer/handling-dynamic-preview-urls-in-cypress-tests-2k91</guid>
      <description>&lt;p&gt;When working with local development servers, a common challenge is dealing with dynamically generated preview URLs. Every time you start the server, a new URL is created, making it tricky to automate tests with tools like Cypress. In this article, I’ll walk you through two approaches to handle this issue and run Cypress tests seamlessly.&lt;br&gt;
&lt;strong&gt;The Problem&lt;/strong&gt;&lt;br&gt;
When you run a local server (e.g., npm run start:local:test), it often generates a dynamic preview URL like &lt;a href="https://random-preview-url.com" rel="noopener noreferrer"&gt;https://random-preview-url.com&lt;/a&gt;. This URL changes every time the server starts, making it difficult to hardcode in your Cypress tests. Here’s how you can tackle this challenge.&lt;br&gt;
**&lt;/p&gt;
&lt;h2&gt;
  
  
  Approach 1
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Run the Server and Capture the Preview URL in Cypress&lt;/strong&gt;&lt;br&gt;
In this approach, we’ll start the server directly within the Cypress test suite and extract the preview URL from the server’s output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`describe('Shopify App Preview Test', () =&amp;gt; {
  let previewUrl;

  before(() =&amp;gt; {
    // Run the server command and capture the output
    cy.exec('npm run start:local:test', { failOnNonZeroExit: true }).then((result) =&amp;gt; {
      // Use a regex to extract the Preview URL from the command output
      const match = result.stdout.match(/Preview URL: (https:\/\/[^\s]+)/);
      if (match) {
        previewUrl = match[1]; // Extracted preview URL
      } else {
        throw new Error('Preview URL not found in server output');
      }
    });
  });

  it('should load the preview page', () =&amp;gt; {
    // Visit the dynamically fetched preview URL
    cy.visit(previewUrl);

    // Add assertions to verify the preview page
    cy.contains('Expected content on the preview page').should('exist'); // Adjust as necessary
  });
});`

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The cy.exec() command runs the server and captures its output.&lt;/li&gt;
&lt;li&gt; A regular expression extracts the preview URL from the server’s logs.&lt;/li&gt;
&lt;li&gt;The extracted URL is used in the test to visit the page and perform 
assertions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Approach 2
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use a Custom Script to Capture the Preview URL&lt;/strong&gt;&lt;br&gt;
In this approach, we’ll use a custom Bash script to start the server, extract the preview URL, and pass it to Cypress as an environment variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create a Bash Script&lt;/strong&gt;&lt;br&gt;
Save the following script as &lt;code&gt;start-server.sh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# Run the server command in the background and capture the output to a log file
npm run start:local:test &amp;gt; server.log &amp;amp;

# Wait for a few seconds to allow the server to start and print the preview URL
sleep 5

# Extract the preview URL from the log file using `grep`
PREVIEW_URL=$(grep -oP 'Preview URL: \Khttps://[^\s]+' server.log)

# Export the preview URL as an environment variable
export CYPRESS_PREVIEW_URL=$PREVIEW_URL

# Run Cypress tests
npx cypress open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Update Your Cypress Test&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;describe('Shopify App Preview Test', () =&amp;gt; {
  let previewUrl;

  before(() =&amp;gt; {
    // Get the preview URL from the environment variable
    previewUrl = Cypress.env('PREVIEW_URL');
  });

  it('should load the preview page', () =&amp;gt; {
    // Visit the dynamically fetched preview URL
    cy.visit(previewUrl);

    // Add assertions as necessary
    cy.contains('Expected content on the preview page').should('exist');
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Bash script starts the server and logs its output.&lt;/li&gt;
&lt;li&gt;It extracts the preview URL using grep and exports it as an environment 
variable.&lt;/li&gt;
&lt;li&gt;Cypress reads the environment variable and uses it in the test.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Handling dynamic preview URLs in Cypress tests doesn’t have to be a headache. By using either of these approaches, you can automate your tests effectively, even when the URL changes every time you start the server. Give them a try and let me know which one works best for you!&lt;/p&gt;

&lt;p&gt;Feel free to share your thoughts or questions in the comments below. Happy testing! 🚀&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>shopify</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Extracting Links from Gmail Emails Using Node.js,Imap and Puppeteer</title>
      <dc:creator>QAProEngineer</dc:creator>
      <pubDate>Thu, 02 Nov 2023 17:49:10 +0000</pubDate>
      <link>https://dev.to/qaproengineer/extracting-links-from-gmail-emails-using-nodejsimap-and-puppeteer-dec</link>
      <guid>https://dev.to/qaproengineer/extracting-links-from-gmail-emails-using-nodejsimap-and-puppeteer-dec</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article will walk you through the process of extracting links from Gmail emails using Node.js and Puppeteer. We'll explore how to set up an IMAP client to access your Gmail inbox, parse email content using the 'mailparser' library, and extract links from the email body using the 'cheerio' library. Additionally, we'll use Puppeteer to interact with and navigate to the extracted links within the emails.&lt;br&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;br&gt;
Before we get started, make sure you have the following prerequisites in place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A Gmail account&lt;/strong&gt;: You will need a Gmail account from which you want to extract links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App Password&lt;/strong&gt;: To access your Gmail account programmatically, you should generate an App Password. This password is used in place of your regular Gmail password and is more secure for applications. We'll explain how to create an App Password in the article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js:&lt;/strong&gt; Ensure you have Node.js installed on your computer.
&lt;strong&gt;Getting Started:&lt;/strong&gt; Creating an App Password&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Gmail has a security feature that prevents the use of your regular password for less secure apps. To work around this, you can create an App Password. Here's how to do it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign in to your Gmail account.&lt;/li&gt;
&lt;li&gt;Go to your Google Account settings:
    - Click on your profile picture in the upper-right corner and select "Google Account."
     - In the left sidebar, click on "Security."&lt;/li&gt;
&lt;li&gt;Under "Signing in to Google," click on "App passwords."&lt;/li&gt;
&lt;li&gt;You may need to sign in again.&lt;/li&gt;
&lt;li&gt;In the "App passwords" section, click "Select app" and choose "Mail" (for email) and "Other (Custom name)" for the device. Enter a custom name (e.g., "Node.js Email Extractor").&lt;/li&gt;
&lt;li&gt;Click "Generate." You'll receive a 16-character app password. Make sure to copy it somewhere safe; you won't be able to see it again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Setting Up the Code:&lt;/strong&gt;&lt;br&gt;
Now that you have an App Password, you can use it to access your Gmail account via IMAP and extract links from emails using Node.js. Here's a breakdown of the code you provided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;emailConfig&lt;/strong&gt;: This object contains your Gmail account information, including the App Password you generated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;magicLinkSubject&lt;/strong&gt;: This is the subject of the email you want to search for.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The code sets up an IMAP client, searches for emails with the specified subject, and then extracts and logs the email subject and body.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It uses 'cheerio' to parse the HTML content of the email and extract links within anchor tags. The links are stored in the links array and logged to the console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Puppeteer is used to launch a browser, navigate to the first extracted link, and log the link.&lt;br&gt;
&lt;strong&gt;Running the Code:&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Make sure you have Node.js installed on your computer.&lt;/li&gt;
&lt;li&gt;Install the required Node.js packages:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install imap mailparser cheerio puppeteer

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Replace the &lt;strong&gt;emailConfig&lt;/strong&gt; object's user and password properties with your Gmail address and the App Password you generated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run the script:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node your-script-filename.js

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;See below the full script:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const cheerio = require('cheerio');
const Imap = require('imap');
const { default: puppeteer} = require('puppeteer');
const simpleParser = require('mailparser').simpleParser;

const emailConfig = {
    user: 'test-email@gmail.com', // Replace with your Gmail email address
    password: 'generated-app-password', // Replace with your Gmail password
    host: 'imap.gmail.com',
    port: 993,
    tls: true,
    tlsOptions: {
        rejectUnauthorized: false, // For testing, you can disable certificate rejection
    },
    connectTimeout: 100000, // 60 seconds 
    authTimeout: 30000,
    debug: console.log,
};

const magicLinkSubject = 'Email subject example';

(async () =&amp;gt; {
    // Set up an IMAP client
    const imap = new Imap(emailConfig);

    imap.once('ready', () =&amp;gt; {
        imap.openBox('INBOX', true, (err) =&amp;gt; {
            if (err) {
                console.error('Error opening mailbox', err);
                imap.end();
                return;
            }

            // Search for emails with the magic link subject
            imap.search([['SUBJECT', magicLinkSubject]], (err, results) =&amp;gt; {
                if (err) throw err;

                const emailId = results[0]; // Assuming the first result is the correct email
                console.log('This is the email address: ' + emailId);
                const email = imap.fetch(emailId, { bodies: '' });

                email.on('message', (msg, seqno) =&amp;gt; {
                    msg.on('body', (stream) =&amp;gt; {
                        simpleParser(stream, async (err, mail) =&amp;gt; {
                            if (err) throw err;

                            // Extract and log the email subject
                            const emailSubject = mail.text;
                            console.log('Email Subject:', emailSubject);
                            // Your code to extract and process the email content here
                            // Extract and log the email body
                            const emailBody = mail.html;
                            console.log('Email Body:', emailBody);
                            //Use cheerio to extract links 
                            const $ =cheerio.load(emailBody);
                            const links=[];
                            $('a').each((index,element)=&amp;gt;{
                                links.push($(element).attr('href'));

                            });
                            console.log('Extracted Links', links);
                            const browser =await puppeteer.launch({headless: false});
                            const page = await browser.newPage();
                            await page.goto(links[0]);
                            console.log('this is the first link'+ links[0]);

                        });
                    });
                });
            });
        });
    });

    imap.connect();

    // Handle errors and edge cases as needed
})();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>puppeteer</category>
      <category>test</category>
      <category>imap</category>
    </item>
    <item>
      <title>How to Deploy Puppeteer with AWS Lambda</title>
      <dc:creator>QAProEngineer</dc:creator>
      <pubDate>Wed, 25 Oct 2023 15:58:46 +0000</pubDate>
      <link>https://dev.to/qaproengineer/how-to-deploy-puppeteer-with-aws-lambda-2goe</link>
      <guid>https://dev.to/qaproengineer/how-to-deploy-puppeteer-with-aws-lambda-2goe</guid>
      <description>&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before we dive into the details, make sure you have the following in place:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An AWS account with access to AWS Lambda and other necessary services.
A Node.js project where you can deploy your Lambda function.
Familiarity with Puppeteer and basic AWS Lambda concepts.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Setting up the Environment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first step is to set up your development environment. In your Node.js project, you'll need to install the **chrome-aws-lambda **package. This package contains a headless Chromium binary optimized for AWS Lambda environments.&lt;br&gt;
&lt;code&gt;npm install chrome-aws-lambda&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Once the package is installed, you can create a Lambda function to run your Puppeteer code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating the AWS Lambda Function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this example, we'll create a Lambda function that navigates to a webpage and returns its title. Here's a sample Lambda function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const chromium = require('chrome-aws-lambda');

exports.handler = async (event, context) =&amp;gt; {
  let result = null;
  let browser = null;

  try {
    browser = await chromium.puppeteer.launch({
      args: chromium.args,
      defaultViewport: chromium.defaultViewport,
      executablePath: await chromium.executablePath,
      headless: chromium.headless,
      ignoreHTTPSErrors: true,
    });

    let page = await browser.newPage();

    await page.goto(event.url || 'https://example.com');

    result = await page.title();
  } catch (error) {
    console.error(error);
  } finally {
    if (browser !== null) {
      await browser.close();
    }
  }

  return result;
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the key parts of this Lambda function:&lt;/p&gt;

&lt;p&gt;We import the chrome-aws-lambda package, which provides an optimized Chromium binary for AWS Lambda.&lt;br&gt;
Inside the Lambda handler, we launch a Puppeteer browser using the chromium.puppeteer.launch method. We pass various options, such as command-line arguments and the path to the Chromium binary.&lt;/p&gt;

&lt;p&gt;1- We create a new page, navigate to a URL (you can specify the URL as an event parameter), and retrieve the page title.&lt;br&gt;
   2-If any errors occur during the process, we log them to the console.&lt;br&gt;
   3- Finally, we return the result, which is the page title in this example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploying the Lambda Function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To deploy the Lambda function, you can use the AWS Management Console, AWS CLI, or a tool like the Serverless Framework. Here, we'll use the AWS CLI as an example:&lt;br&gt;
Ensure you have the AWS CLI configured with the necessary permissions.&lt;br&gt;
Create a deployment package by packaging your Node.js code and its dependencies. In your project directory, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;zip -r function.zip node_modules/ your-function.js

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1-Replace &lt;strong&gt;your-function.js&lt;/strong&gt; with the name of your Lambda function file.&lt;br&gt;
Create the Lambda function using the AWS CLI. Replace  with the appropriate IAM role ARN that gives Lambda permissions to access other AWS resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws lambda create-function --function-name YourFunctionName \
  --zip-file fileb://function.zip \
  --handler your-function.handler \
  --runtime nodejs14.x \
  --role &amp;lt;ROLE_ARN&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2-Invoke your Lambda function using the AWS CLI or any other method you prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws lambda invoke --function-name YourFunctionName output.txt

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;strong&gt;YourFunctionName&lt;/strong&gt; with the name of your Lambda function. You should see the title of the webpage in the &lt;strong&gt;output.txt&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Running Puppeteer in an AWS Lambda function can be a powerful way to automate browser tasks on a serverless infrastructure. With chrome-aws-lambda, you can use an optimized Chromium binary, reducing the cold start time and improving the overall performance of your Lambda functions. This can be useful for various use cases, including web scraping, automated testing, and generating PDFs from web pages. Just keep in mind that AWS Lambda has some limitations, such as execution time limits and memory restrictions, so it's important to design your functions accordingly.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>awslambda</category>
      <category>puppeteer</category>
      <category>aws</category>
    </item>
    <item>
      <title>Step-by-Step Guide to Setting Up Sauce Labs CI/CD with GitHub Actions for mobile App testing</title>
      <dc:creator>QAProEngineer</dc:creator>
      <pubDate>Tue, 26 Sep 2023 15:23:40 +0000</pubDate>
      <link>https://dev.to/qaproengineer/step-by-step-guide-to-setting-up-sauce-labs-cicd-with-github-actions-for-mobile-app-testing-4a7n</link>
      <guid>https://dev.to/qaproengineer/step-by-step-guide-to-setting-up-sauce-labs-cicd-with-github-actions-for-mobile-app-testing-4a7n</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sauce Labs is a cloud-based platform for automated testing of web and mobile applications. In this guide, we'll walk through the steps to set up a CI/CD (Continuous Integration/Continuous Deployment) pipeline using GitHub Actions to perform end-to-end (E2E) testing of a mobile app with Sauce Labs. The GitHub Actions workflow is defined in a YAML file (&lt;code&gt;.github/workflows/saucelabs.yml&lt;/code&gt;), and it consists of several steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Prerequisites&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before getting started, make sure you have the following prerequisites in place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A GitHub repository containing your mobile app source code.&lt;/li&gt;
&lt;li&gt;A Sauce Labs account with &lt;code&gt;SAUCE_USERNAME&lt;/code&gt; and &lt;code&gt;SAUCE_ACCESS_KEY&lt;/code&gt; credentials.&lt;/li&gt;
&lt;li&gt;The mobile app file (APK in this example) you want to test.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;GitHub Repository Setup&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a &lt;code&gt;.github/workflows&lt;/code&gt; directory in your GitHub repository if it doesn't already exist.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inside the &lt;code&gt;.github/workflows&lt;/code&gt; directory, create a YAML file (e.g., &lt;code&gt;saucelabs.yml&lt;/code&gt;) to define your CI/CD workflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy and paste the following YAML code into your &lt;code&gt;saucelabs.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="na"&gt;Sauce CI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;app E2E testing&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;push&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;SAUCE_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SAUCE_USERNAME }}&lt;/span&gt;
  &lt;span class="na"&gt;SAUCE_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SAUCE_ACCESS_KEY }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout Repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set Up Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;16.13.0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload App to Sauce Labs&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;SAUCE_USERNAME=$SAUCE_USERNAME&lt;/span&gt;
          &lt;span class="s"&gt;SAUCE_ACCESS_KEY=$SAUCE_ACCESS_KEY&lt;/span&gt;
          &lt;span class="s"&gt;APP_FILE_PATH=$(pwd)/app-upload/app-release-2.0.5.apk&lt;/span&gt;

          &lt;span class="s"&gt;response=$(curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" --location \&lt;/span&gt;
          &lt;span class="s"&gt;--request POST 'https://api.us-west-1.saucelabs.com/v1/storage/upload' \&lt;/span&gt;
          &lt;span class="s"&gt;--form "payload=@$APP_FILE_PATH" \&lt;/span&gt;
          &lt;span class="s"&gt;--form 'name="app-x86-release.apk"' \&lt;/span&gt;
          &lt;span class="s"&gt;--form 'description="Android Test App v3"')&lt;/span&gt;

          &lt;span class="s"&gt;# Extract the file ID from the response&lt;/span&gt;
          &lt;span class="s"&gt;file_id=$(echo "$response" | jq -r '.item.id')&lt;/span&gt;

          &lt;span class="s"&gt;# Print the file_id value&lt;/span&gt;
          &lt;span class="s"&gt;echo "File ID to be deleted: $file_id"&lt;/span&gt;

          &lt;span class="s"&gt;# Store the file_id in a file&lt;/span&gt;
          &lt;span class="s"&gt;echo "$file_id" &amp;gt; file_id.txt&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Change to e2e/testing directory&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cd e2e/testing&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cd e2e/testing&lt;/span&gt;
          &lt;span class="s"&gt;npm install&lt;/span&gt;
          &lt;span class="s"&gt;npm run test:android:sauce:emu &lt;/span&gt;
        &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;# Continue even if tests fail&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Delete App from Sauce Labs&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;SAUCE_USERNAME=$SAUCE_USERNAME&lt;/span&gt;
          &lt;span class="s"&gt;SAUCE_ACCESS_KEY=$SAUCE_ACCESS_KEY&lt;/span&gt;

          &lt;span class="s"&gt;# Read the file_id from the stored file&lt;/span&gt;
          &lt;span class="s"&gt;file_id=$(cat file_id.txt)&lt;/span&gt;

          &lt;span class="s"&gt;# Print the file_id value&lt;/span&gt;
          &lt;span class="s"&gt;echo "File ID to be deleted: $file_id"&lt;/span&gt;

          &lt;span class="s"&gt;# Add a delay of 10 seconds&lt;/span&gt;
          &lt;span class="s"&gt;sleep 10&lt;/span&gt;

          &lt;span class="s"&gt;curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" --location \&lt;/span&gt;
          &lt;span class="s"&gt;--request DELETE "https://api.us-west-1.saucelabs.com/v1/storage/files/$file_id"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Logs on Failure&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;failure()&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;logs&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;logs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what each step in the workflow does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Checkout Repository&lt;/strong&gt;: This step checks out the code from your GitHub repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set Up Node.js&lt;/strong&gt;: Installs Node.js with the specified version (16.13.0 in this case) on the GitHub runner.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Upload App to Sauce Labs&lt;/strong&gt;: Uploads your mobile app (APK file) to Sauce Labs storage and stores the file ID in a text file for future reference.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Change to e2e/testing directory&lt;/strong&gt;: Navigates to the directory where your end-to-end tests are located.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install Dependencies&lt;/strong&gt;: Installs project dependencies and runs the E2E tests for the Android app on Sauce Labs Android emulators. The &lt;code&gt;continue-on-error&lt;/code&gt; flag is set to true to allow the workflow to continue even if the tests fail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Delete App from Sauce Labs&lt;/strong&gt;: Deletes the app from Sauce Labs storage using the file ID stored earlier. This is optional but helps clean up storage after the tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Upload Logs on Failure&lt;/strong&gt;: If any step in the workflow fails (e.g., test failures), this step uploads the logs as artifacts for debugging.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitHub Secrets
&lt;/h2&gt;

&lt;p&gt;To securely store your Sauce Labs credentials (&lt;code&gt;SAUCE_USERNAME&lt;/code&gt; and &lt;code&gt;SAUCE_ACCESS_KEY&lt;/code&gt;), you should add them as secrets in your GitHub repository. To add secrets, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to your GitHub repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on "Settings" &amp;gt; "Secrets" &amp;gt; "New repository secret."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a secret with the name &lt;code&gt;SAUCE_USERNAME&lt;/code&gt; and set its value to your Sauce Labs username.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add another secret with the name &lt;code&gt;SAUCE_ACCESS_KEY&lt;/code&gt; and set its value to your Sauce Labs access key.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With this GitHub Actions workflow, you can automate the E2E testing of your mobile app using Sauce Labs. When you push changes to your repository, GitHub Actions will trigger the workflow, and you'll receive test results and logs in case of failures. This setup helps ensure the quality and reliability of your mobile application through automated testing with Sauce Labs.&lt;/p&gt;

&lt;p&gt;Remember to customize the workflow according to your specific project requirements, such as adjusting the Node.js version or the paths to your app and test files.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Analyzing data accurancy with Puppeteer and Axios( example: book prices )</title>
      <dc:creator>QAProEngineer</dc:creator>
      <pubDate>Wed, 13 Sep 2023 19:30:34 +0000</pubDate>
      <link>https://dev.to/qaproengineer/analyzing-book-prices-with-puppeteer-and-axios-example-book-prices--4hbm</link>
      <guid>https://dev.to/qaproengineer/analyzing-book-prices-with-puppeteer-and-axios-example-book-prices--4hbm</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const puppeteer = require("puppeteer");
const axios = require("axios");

const main = async () =&amp;gt; {
  // Launch a new browser instance
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: null,
  });

  // Create a new page
  const page = await browser.newPage();

  // Navigate to the book product details page
  const baseUrl = "https://www.example.com/book-top-100?p=";

  // define the number of pages to iterate through.
  const pageCount = 2;

  // Create a new array for the results
  const results = [];

  //Iterate through each page
  for (let i = 1; i &amp;lt; pageCount; i++) {
    //construct the URL for the current page
    const currentUrl = `${baseUrl}${i}`;
    //navigate to the page
    await page.goto(currentUrl);

    // get all the product listing urls
    const urls = await page.$$eval("li.item.product &amp;gt; div &amp;gt; a", (links) =&amp;gt;
      links.map((link) =&amp;gt; link.href)
    );

    for (const url of urls) {
      await page.goto(url);

      // Get all the swatches for the book
      const swatchElements = await page.$$("p.label");

      // Loop through each swatch and click on it to show the price and SKU
      for (let swatchElement of swatchElements) {
        await swatchElement.click();

        // Extract the price and SKU
        const sku = await page.$eval(".col.data.isbn_13", (elem) =&amp;gt;
          elem.textContent.trim()
        ); // extract the SKU for each selection of format.

        let priceText;
        const swatchElementType = await swatchElement.evaluate((el) =&amp;gt;
          el.textContent.trim()
        );
        if (
          swatchElementType.includes("Audiobook") ||
          swatchElementType.includes("eBook")
        ) {
          priceText = await page.$$eval(
            ".price-swatch span.price, .normal-price",
            (prices) =&amp;gt; prices.map((price) =&amp;gt; price.textContent.trim())
          );
        } else if (
          swatchElementType.includes("Paperback") ||
          swatchElementType.includes("Hardcover")
        ) {
          priceText = await page.$$eval(
            "p.old-price, span.old-price",
            (prices) =&amp;gt; prices.map((price) =&amp;gt; price.textContent.trim())
          );
        }

        const price = priceText.flatMap((price) =&amp;gt;
          price.split("$").filter(Boolean)
        ); // extract the numerical price values for this product

        // Make an API call to API to extract the price values for this product then compare them against the web prices.
        const api_book = `https://api.example.com/products/${sku}`;
        const response = await axios.get(api_book);

        const apiPrice = response.data[0].price_amount;
        const webPrice = Number.parseFloat(price);

        console.log(
          "this the api price=" + apiPrice + " and " + "web price=" + price
        );
        if (webPrice == apiPrice) {
          console.log(`Price for SKU ${sku} matches in web and api price`);
        } else {
          console.log(`Price for SKU ${sku} does not match in web and API`);
          // Add the results to the array
          results.push({
            price,
            apiPrice,
            sku,
          });
        }
      }
    } // end url loop
  }
  //Append The results to a string with new lines
  const resultString = results
    .map(
      (result) =&amp;gt;
        `Price: ${result.price}\nAPI Price: ${result.apiPrice}\nSKU: ${result.sku}\n`
    )
    .join("\n");
  // Close the browser instance
  await browser.close();

  // Write the results to a JSON file
  const fs = require("fs");
  fs.writeFileSync("book-prices-details.json", JSON.stringify(resultString));
};

main();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Introduction:&lt;/strong&gt;&lt;br&gt;
In today's digital age, it's essential for businesses to stay competitive, especially when it comes to pricing their products. One way to do this is by regularly comparing your product prices with those of your competitors. This blog post will explain the purpose of a Puppeteer script that automates the process of extracting book prices from a website and comparing them with prices from an API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Puppeteer and Axios:&lt;/strong&gt;&lt;br&gt;
Puppeteer is a headless browser automation tool that allows you to control and interact with web pages programmatically. Axios is a popular JavaScript library for making HTTP requests. Together, they provide a powerful toolset for web scraping and data extraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Script Overview:&lt;/strong&gt;&lt;br&gt;
The provided Puppeteer script has several key objectives:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.Launch a headless web browser using Puppeteer.
2.Navigate to a specific website that lists the top 100 books.
3.Iterate through multiple pages of book listings.
4.Extract the URLs of individual book product pages.
5.For each book product page, extract the price and SKU information for different book formats (e.g., Audiobook, eBook, Paperback, Hardcover).
6.Make API calls to a price comparison service to fetch the correct prices for the books.
7.Compare the web prices with the API prices to identify any discrepancies.
8.Store the results in a structured format (JSON file).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;br&gt;
This Puppeteer and Axios script automates the process of comparing book prices between a website and an API, helping businesses make informed pricing decisions. It demonstrates the power of web scraping and data extraction with Puppeteer and the ease of making API requests with Axios. By regularly running such scripts, businesses can stay competitive and ensure their pricing remains competitive in the market.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>scrap</category>
      <category>webscraping</category>
      <category>puppeteer</category>
    </item>
    <item>
      <title>Write a Playwright test using AWS Lambda</title>
      <dc:creator>QAProEngineer</dc:creator>
      <pubDate>Mon, 11 Sep 2023 16:08:26 +0000</pubDate>
      <link>https://dev.to/qaproengineer/write-a-playwright-test-using-aws-lambda-3edj</link>
      <guid>https://dev.to/qaproengineer/write-a-playwright-test-using-aws-lambda-3edj</guid>
      <description>&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a Playwright Test Script:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Write your Playwright test script in a Node.js environment. Here's a simple example using Playwright to open a webpage and take a screenshot:&lt;br&gt;
`const { chromium } = require('playwright');&lt;/p&gt;

&lt;p&gt;exports.handler = async (event) =&amp;gt; {&lt;br&gt;
  let browser;&lt;br&gt;
  try {&lt;br&gt;
    browser = await chromium.launch();&lt;br&gt;
    const context = await browser.newContext();&lt;br&gt;
    const page = await context.newPage();&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await page.goto('https://example.com');
await page.screenshot({ path: '/tmp/example.png' });

await browser.close();

return {
  statusCode: 200,
  body: JSON.stringify({ message: 'Screenshot taken!' }),
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;} catch (error) {&lt;br&gt;
    console.error('Error:', error);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
  statusCode: 500,
  body: JSON.stringify({ error: 'Internal Server Error' }),
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;} finally {&lt;br&gt;
    if (browser) await browser.close();&lt;br&gt;
  }&lt;br&gt;
};`&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Package Dependencies:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since AWS Lambda has limited space for dependencies, you should use a tool like serverless or AWS SAM to package your Lambda function along with its dependencies. Ensure that you package Playwright and any other required Node.js modules.&lt;/p&gt;

&lt;p&gt;3.&lt;strong&gt;Deploy to AWS Lambda:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use AWS CLI or a deployment tool like serverless or AWS SAM to deploy your Lambda function to AWS.&lt;/p&gt;

&lt;p&gt;4.&lt;strong&gt;Configure Lambda Function:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the AWS Lambda console, set up your function. You'll need to configure the following:&lt;/p&gt;

&lt;p&gt;** Runtime:** Node.js (Choose the version according to your script's requirements).&lt;br&gt;
   ** Handler:** The name of your Lambda function and the exported function (e.g., filename.handler).&lt;br&gt;
    &lt;strong&gt;Memory:&lt;/strong&gt; Set memory as per your requirements.&lt;br&gt;
   ** Timeout:** Set the timeout duration for your function. Ensure it's long enough to complete your Playwright test.&lt;br&gt;
   ** Execution Role:** Create an IAM role that allows the Lambda function to execute other AWS services and resources as needed.&lt;/p&gt;

&lt;p&gt;5.&lt;strong&gt;Create an API Gateway (Optional):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to trigger your Lambda function via an HTTP request, you can create an API Gateway and configure it to invoke your Lambda function.&lt;/p&gt;

&lt;p&gt;6.&lt;strong&gt;Testing the Lambda Function:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can now test your Lambda function by invoking it either through the AWS Lambda console or by making an HTTP request if you set up API Gateway.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>aws</category>
      <category>lamda</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
