
Welcome to Modern API Testing with Python and AI.
In this opening lecture, students meet the instructor and get the big-picture promise of the course: building a real, professional API testing framework with Python, Pytest, a real application, GitHub repositories, reporting, and CI CD execution.
The course uses a job tracking application as the system under test. Students will work with the API behind that application, review the Swagger documentation, run the app locally, and write automation against real endpoints instead of only practicing isolated scripts or basic status-code checks.
This introduction explains why API testing is a major skill for QA automation engineers and SDETs. Modern applications often have many APIs behind a single user interface, so strong API testing skills are valuable for testing backend behavior, validating workflows, and supporting reliable releases.
Students will build the framework from scratch so they understand every major part: project structure, configuration, API clients, helpers, fixtures, reports, test documentation, and GitHub Actions. The final result is something students can put in a portfolio, discuss in interviews, and adapt to real work.
The lecture also sets expectations for the style of the course. This is real coding, not highly edited demo content. Mistakes, debugging, and troubleshooting are part of the learning process because they reflect what happens in real automation work.
AI will be used as an assistant later in the course, especially for documentation, docstrings, test case tracking, pipeline work, and eventually code generation with human review. But students will first learn how to design and write API tests manually so they can judge the work correctly.
Application repository: https://github.com/supersqa1/job-tracker-app-for-testing
In this lecture, we walk through the full table of contents for the API Testing 2026 course so students know exactly what they will build and which sections they can skip if they already have experience.
The course starts with a welcome, a preview of the application under test, and API basics such as requests, responses, status codes, and what an API call looks like. From there, students set up their environment with Python, an IDE such as Cursor, and the API application that will be tested throughout the course.
The lecture explains that Pytest is the test runner and engine for the framework. Students get a Pytest crash course before building the actual API testing framework from an empty folder. The framework grows step by step with test structure, API clients, helper methods, more tests, SQL and database validation, negative testing, documentation, test case IDs, and reporting.
The course also includes practical AI usage. Early sections use AI mostly for documentation, test case tracking, and docstrings while students still write the automation by hand. Later sections use AI more like a real-world engineering assistant after the fundamentals are in place.
The table of contents also highlights CI CD with GitHub Actions, plus Pytest features like parameterization and fixtures. By the end, students will have a professional API testing framework with roughly 80 to 90 automated tests that can run locally and in CI CD.
This lecture sets expectations: the course is hands-on, practical, and focused on building a real framework students can understand, explain, and eventually use as portfolio proof.
In this lecture, students get a quick preview of the end result they will build in the API Testing 2026 course.
The video shows the application under test, a job tracker application with a frontend, backend APIs, and Swagger documentation. Students see why API testing matters: the frontend communicates with the backend through APIs, and our job is to verify that those interactions work correctly.
The lecture also introduces the broader real-world context. The application is an open-source job tracking project, and the API tests are part of a professional software development lifecycle. Students will eventually clone the application, run it locally, write automation against it, and connect those tests to a CI CD pipeline.
Students also see the final automation outcome: a Pytest-based API testing framework, GitHub Actions running the tests in CI CD, around 85 passing tests, and an HTML report generated from the test run. The same suite can run locally from the IDE and in the pipeline.
The lecture makes it clear that this is not just a small tutorial exercise. The goal is to build a scalable API testing framework that includes real application context, API documentation, local execution, CI CD execution, reporting, and a portfolio-ready project students can explain with confidence.
Students may use Cursor, VS Code, IntelliJ, or another IDE. AI will be introduced as a practical assistant later in the course, but the foundation of API testing will be built manually first so students understand what the automation is doing.
Application repository: https://github.com/supersqa1/job-tracker-app-for-testing
In this short section introduction, we set expectations for the API overview section of the course.
This section is designed for students who are new to APIs, new to API testing, or newer to software and QA automation in general. You will get a beginner-friendly explanation of what APIs are, why applications use them to communicate with each other, and why API testing is such an important skill in modern QA work.
If you already have experience using APIs, testing APIs, or working with tools like Postman and Swagger, you may choose to move through this section quickly. But if you are still building your foundation, this section will help you understand the concepts before we go deeper into hands-on testing.
In this lecture, we build a practical understanding of what APIs are by looking at real examples of applications talking to other applications.
You will see how a frontend communicates with a backend through requests and responses, why APIs are central to modern web and mobile applications, and how services like Google Maps, social media platforms, AI providers, and our course job tracker app use APIs behind the scenes.
We also introduce important API testing concepts such as HTTP requests, responses, status codes, JSON data, and why backend/API testing creates so many opportunities for QA automation engineers.
In this lecture, we break down the anatomy of an HTTP API request and response so you understand what you are actually testing when you test APIs.
You will learn the major parts of a request, including the method, URL, endpoint, headers, authorization, query parameters, and body or payload. Then we look at the response side, including status codes, response body, headers, and response time.
We also make real API calls from Swagger against the course job tracker API, including a simple health check and a user registration request. This gives you a practical preview of how requests are built, how JSON data is sent and returned, and why these pieces matter when we later automate API tests with Python.
In this short section introduction, we set expectations for the Python and IDE setup section of the course.
This section is for students who still need to confirm their development environment is ready before we start the hands-on API testing work. You will learn what tools you need, including Python 3 and a Python-friendly IDE such as Cursor, VS Code, Antigravity, IntelliJ, or another editor you already prefer.
If you already have Python installed and you are comfortable with your IDE, this section is optional and you can move ahead faster. If you are newer or want to follow the instructor’s setup more closely, continue through the next videos where we discuss IDE options and install Python on Mac and Windows.
In this lecture, we install and verify Python on macOS so your machine is ready for the API testing work we will do later in the course.
You will learn how to check whether Python is already installed, how to tell the difference between the old default `python` command and the modern `python3` command, and how to install Python from python.org if your Mac does not already have a usable Python 3 version.
We also look at practical terminal commands like `python --version`, `python3 --version`, and `which -a python3` so you can understand which Python version your system is actually using. If your terminal does not recognize Python immediately after installation, you will also see why opening a new terminal window can refresh your PATH and fix the issue.
In this lecture, we install and verify Python on Windows so your machine is ready for the API testing work we will do later in the course.
You will learn how to check whether Python is already installed using Command Prompt, how the `python` and `python3` commands behave differently on Windows, and how to use `where python` to see which Python installations are available on your system.
We also walk through installing Python from python.org, including what to do if the newer install manager gives you problems, why the standalone `.exe` installer may be easier, and why selecting the option to add Python to PATH is important. Finally, we verify the installation in a fresh terminal and look at where to manually update environment variables if Python is not found from the command line.
In this section intro, we set up the API application that will be used throughout the course.
You will see what the Job Tracker application is, why we are using a real API instead of toy examples, and how the app connects the frontend, backend, Swagger documentation, and API endpoints we will test later.This section prepares you to run the API locally on your own machine. You will get the public GitHub repository, run the application, open the local UI, explore Swagger, and use Postman to make API calls.
The app has been packaged so you can focus on API testing instead of frontend setup: the backend serves the already-built frontend, and the upcoming videos include both a quick-start path and a more detailed troubleshooting path.If you already have the API running locally, you can move faster through this section.
If not, follow the setup videos carefully because the rest of the course depends on having this API available for exploration, manual testing, automation, bug discovery, and documentation.Course API repository: https://github.com/supersqa1/job-tracker-app-for-testing
In this lecture, we do the fast version of getting the course API under test running on your machine.
You will clone or download the job tracker API repository, run the provided startup script, and verify that the application is working by opening the local app and Swagger API documentation in the browser.
We cover both Mac and Windows. On Mac, you run the shell script, and on Windows, you run the batch file or PowerShell script. The script checks for Python, creates a virtual environment, installs the required dependencies, copies the needed configuration file, and starts the API for you.
If everything works, your API is ready and you can skip the longer detailed setup video. If you run into problems, the next lecture breaks the same process down with more explanation and manual troubleshooting.
Course API repository: https://github.com/supersqa1/job-tracker-app-for-testing
In this lecture, we take the detailed path for running the API under test on your local machine.
You will learn why every student runs their own local copy of the course API, how to clone or download the job tracker application, and how to start it on both Mac and Windows using the provided scripts.
We also walk through what the startup scripts are doing behind the scenes: checking Python, creating a virtual environment, installing dependencies, copying the environment configuration file, and starting the local server. If the automated script does not work, you will see the manual setup process and common troubleshooting steps, including what to do when a port is already in use.
By the end, you should be able to open the local app, access the Swagger documentation, run a quick health check, and restart the API whenever you come back to the course.
Course API repository: https://github.com/supersqa1/job-tracker-app-for-testing
In this lecture, we explore the Swagger documentation for the course API and learn how it helps us understand, test, and inspect API behavior.
You will see how Swagger is generated from the OpenAPI specification, how to access it from the local `/docs` endpoint, and how it gives us more than just a list of URLs. Swagger helps us understand endpoints, methods, request payloads, response examples, required fields, schemas, authentication requirements, and status codes.
We also make real API calls directly from the Swagger UI, including public health/status requests, user registration, login, and authenticated requests. Along the way, you will see how Swagger can help you discover test ideas such as required-field checks, data length validation, negative scenarios, and authentication behavior.
In this lecture, we do a practical overview of Postman and how it fits into our API testing workflow.
You will learn why Postman is useful for manually exploring and validating API behavior before writing automation, while still understanding that our main automation framework in this course will be Python with pytest.
We cover the desktop Postman app, why it is better for local `localhost` APIs, how to import requests from Swagger using cURL, how to save requests into collections, and how to import the full OpenAPI JSON specification into Postman. We also look at environments, base URL variables, authentication, and why saved requests make API exploration easier than constantly rebuilding calls from scratch.
In this section introduction, we set up the purpose of the Pytest crash course before moving into the main API automation framework.
You will learn why Pytest matters, why we are using it as the foundation for this course, and how a testing framework helps us run many tests consistently instead of executing files one at a time.
This section focuses on the practical Pytest basics you need for the rest of the course: how Pytest discovers tests, how we name files and functions, how we run tests, and how Pytest can later support reporting, plugins, parallel execution, and CI/CD workflows.
If you already know Pytest well, you can move through this section quickly. If Pytest is new to you, this crash course will give you the foundation needed before we build the API testing framework on top of it.
In this lecture, we take the detailed path for running the API under test on your local machine.
You will learn why every student runs their own local copy of the course API, how to clone or download the job tracker application, and how to start it on both Mac and Windows using the provided scripts.
We also walk through what the startup scripts are doing behind the scenes: checking Python, creating a virtual environment, installing dependencies, copying the environment configuration file, and starting the local server. If the automated script does not work, you will see the manual setup process and common troubleshooting steps, including what to do when a port is already in use.
By the end, you should be able to open the local app, access the Swagger documentation, run a quick health check, and restart the API whenever you come back to the course.
Course API repository: https://github.com/supersqa1/job-tracker-app-for-testing
In this lecture, we learn how Pytest discovers which files, functions, and classes should be treated as tests.
You will see the core naming rules Pytest uses: test files should start with test_ or end with _test.py, test functions should start with test_, and test classes should start with Test using an uppercase T.
We create simple test files and functions, run Pytest against the tests folder, and watch how the collected test count changes based on naming. You will also see how helper functions are ignored when they do not follow the test_ naming convention, and how Pytest reports passing and failing tests.
By the end, you will understand why naming matters, how to avoid the common “no tests found” problem, and how Pytest decides what to run when you execute your test suite.
In this lecture, we look at the different ways to run tests with Pytest once your test files follow the correct naming and discovery rules.
You will learn how to run all tests, run tests from a specific folder, run a specific file, and target one function, class, or method using Pytest node IDs with double-colon syntax.
We also cover practical filtering options like `-k` for partial name matching and `-m` for markers. These options become especially useful when your test suite grows, when you only want to run login or application tests, or when you need to control which tests run in CI/CD tools like Jenkins or GitHub Actions.
By the end, you will understand the main Pytest run commands and how to choose the right command for local development, debugging, targeted test runs, and future automation pipelines.
In this lecture, we begin the main API automation work by documenting the test cases we want to automate.
Before writing automated tests, we first define what we are testing manually: the endpoint, expected behavior, priority, preconditions, steps, and expected results. This gives us a clear testing target before we start writing Python code.
To keep the workflow simple and universal, we use a CSV or spreadsheet instead of a dedicated test case management tool like TestRail, TestLink, or Qase. We create initial API test cases for public endpoints such as the app status and demo stats endpoints, including the expected `200` response and response body structure.
By the end, you will have the starting test case documentation for the Job Tracker API and a practical workflow for turning manual API checks into automated tests one by one.
In this lecture, we create the GitHub repository that will hold the API testing framework code for the rest of the course.
You will learn why the framework should live in its own repository, separate from the API application we are testing. The app repository is provided for you, but this automation repository is the project you will build and eventually use as portfolio evidence of your API testing skills.
We walk through creating a new GitHub repository, choosing public or private visibility, adding a README so the main branch is created automatically, and cloning the repository to your local machine with SSH or HTTPS. We also open the project in an IDE such as Cursor, VS Code, or PyCharm and verify the setup with basic Git commands like `git branch` and `git status`.
By the end, your automation repository will be created, cloned locally, opened in your IDE, and ready for the framework structure we build next.
In this lecture, we start automating the first API test by creating the initial skeleton of the testing framework.
You will create a new Git branch for the work, add a simple project structure, move the test case CSV into a `docs` folder, create a `tests` folder, and add a `requirements.txt` file with `pytest` as the first dependency.
We also create the first Pytest test file using the correct `test_` naming convention, add a placeholder assertion with `assert True`, create and activate a virtual environment, install dependencies from `requirements.txt`, and run the skeleton test successfully.
Finally, we add a simple `.gitignore`, review what should and should not be committed, commit the framework skeleton, push the branch, create a pull request, and merge it. By the end, the repository has the first clean framework structure in place and is ready for a real API call in the next video.
In this lecture, we turn the first placeholder Pytest test into a real automated API test for the public status endpoint.
You will manually inspect the endpoint response in Swagger, identify what should be verified, and then write Python code that sends a `GET` request with the `requests` library. We add `requests` to `requirements.txt`, install the dependency, and handle the common `ModuleNotFoundError` that happens when a third-party package is missing.
We verify the response status code, parse the JSON response body, check important fields like app name, API version, environment, and server time, and discuss when to check exact values versus only checking that a dynamic value exists. We also use `breakpoint()` and PDB to inspect variables during test execution.
By the end, the public status endpoint test is automated, passing, intentionally failure-checked, committed, pushed, reviewed in a pull request, and merged back into the framework.
In this lecture, we automate another public API endpoint: the demo stats endpoint.
You will inspect the endpoint in Swagger, review the expected `200` response, and decide what makes sense to validate for this kind of API. Instead of hard-coding exact count values that may change, we focus on verifying the response structure and checking that returned values are the correct data types.
We create a new Pytest file for the demo stats endpoint, use the `requests` library to send a `GET` request, assert the status code, parse the JSON response body, verify the expected top-level keys, and loop through the `status_counts` dictionary to confirm each count value is an integer.
We also use `breakpoint()`, `dir()`, and pretty-printing to inspect the response object and understand how to discover useful attributes like `status_code` and methods like `json()`. By the end, the second API test is automated and we identify the next framework improvement: removing hard-coded base URLs from tests.
In this lecture, we improve the API testing framework by removing hard-coded base URLs from individual tests.
You will see why hard-coding the full API URL in every test becomes a problem as the suite grows, especially when the same tests need to run against local, test, staging, or production environments.
We create a `configs` folder and a `settings.py` file, read the API base URL from an environment variable with `os.getenv()`, and provide a default local value so the tests can still run easily during development. Then we import the shared `base_url` into existing tests and build endpoint-specific URLs with f-strings instead of repeating the same host and port everywhere.
We also run the tests before and after the change, intentionally change the port to prove the config is being used, and confirm that both public endpoint tests pass after the refactor.
By the end, the framework has a cleaner configuration pattern that supports different environments without changing test code.
In this lecture, we automate a positive login API test and introduce the authentication workflow that future protected endpoint tests will depend on.
You will inspect the login endpoint in Swagger, use a known seeded test account from the app documentation, and send a `POST` request with a JSON payload using the `requests` library. We verify the login returns a `200` response, parse the JSON response body, and validate important authentication fields such as `access_token`, `token_type`, `expires_in`, and the returned user object.
We also discuss why the access token matters, how the UI stores and reuses it for follow-up API calls, why token expiration matters, and how future tests may use SQL or database setup to validate expiration scenarios.
By the end, the framework has a working positive login test, better test-folder organization with `auth` and `public` areas, and a few next refactoring targets: avoiding repeated base URL imports and moving hard-coded login test data into a better place.
In this lecture, we automate a negative login API test for invalid credentials.
You will use the same login endpoint from the positive test, but send an invalid password so the API should reject the request. We inspect the behavior in Swagger first, confirm the expected `401 Unauthorized` response, and then add a second test to the existing login test file instead of creating unnecessary duplicate structure.
We verify more than just the status code. The test also parses the JSON response body and checks that the API returns a useful error object, including the expected error code, message, and details field. This reinforces an important API testing habit: negative tests should validate how the API fails, not only that it fails.
We also make the test fail on purpose to confirm the assertion messages are useful, fix f-string assertion messages, and keep the validation readable.
By the end, the login endpoint has both positive and negative coverage, and the framework has another practical example of validating response status and body together.
In this lecture, we step back from coding and look at how authenticated API requests work.
You will see why protected endpoints cannot be called the same way as public endpoints. We use Swagger to call a protected applications endpoint without authentication first, observe the `401 Unauthorized` response, then log in through the auth endpoint to get an access token.
From there, we authorize Swagger with the JWT bearer token and repeat the protected API call. This shows how authentication changes the request headers and why future automated tests must log in, capture the token, and include it in the `Authorization` header for protected endpoints.
We also preview upcoming application endpoint tests, including list/query behavior, optional filters, pagination parameters, and the difference between a simple smoke check and deeper validation against the database as the source of truth.
By the end, you understand the manual authentication flow that we will automate next: log in, get the token, send the token in the header, and verify protected endpoints respond successfully.
In this lecture, we automate the authenticated `GET /applications` endpoint, which lists job applications for a logged-in user.
You will create a new applications test area, log in first to get a JWT access token, and then send that token in the `Authorization` header for the follow-up API request. This continues the same bearer-token authentication pattern from the previous lesson, but now we apply it directly in code.
We intentionally keep this as a simple smoke test. The test verifies that the authenticated request returns `200`, confirms the response body is not empty, and checks that the response is a list. We also inspect the unauthenticated behavior first, see the `401` response when no header is sent, then add the proper header and confirm the endpoint succeeds.
By the end, you have an automated authenticated test for `GET /applications` and understand the next improvement: reducing repeated login/token setup code before adding more protected endpoint tests.
In this lecture, we continue testing protected endpoints by automating `GET /applications/summary`.
This is not the first authenticated endpoint test. By this point, we already know the bearer-token pattern: log in, capture the JWT token, and send it in the request header. Now we apply that same workflow to the applications summary endpoint.
You will manually inspect the endpoint in Swagger, confirm that it requires authentication, and review the simple `GET` request with no required parameters. Then we automate the call by logging in, sending the token in the `Authorization` header, verifying the response status code is `200`, and checking the response body.
For now, this stays intentionally focused as a smoke test. We verify that expected summary fields exist and that their values are integers, without going deeper into database-backed validation yet. We also call out the framework smell that is now becoming obvious: login payload and token-generation code are being repeated across multiple authenticated tests.
By the end, you have another protected endpoint covered and a clear reason to refactor authentication into a shared helper next.
In this lecture, we clean up repeated authentication code by moving login logic into a reusable helper.
After writing multiple protected endpoint tests, the same pattern is now showing up in more than one place: build a login payload, call the login endpoint, capture the JWT access token, and pass it into the next request. Instead of copying that code into every authenticated test, we create a `helpers` folder and an `auth_helper.py` file with a `login_user()` function.
You will see how the helper returns the access token, how the tests import and call that function, and how this makes each protected endpoint test focus on the endpoint behavior instead of the mechanics of logging in. We also improve the design by moving default user values into configuration so different environments or special test users can be handled without rewriting every test.
By the end, the framework is cleaner, more reusable, and better prepared for many more authenticated API tests.
In this section intro, we introduce the API client layer that will make the test framework cleaner and easier to maintain.
Right now, the tests call the `requests` module directly, check status codes, and parse JSON responses inside each test. That works, but it creates repeated code across the suite. In this section, we move that repeated request logic into a reusable API client class.
You will see why an API client is a standard framework pattern: tests can make one clean method call, while the client handles common behavior like making the request, checking the status code, returning JSON, and preserving flexibility when a test needs the full response object or headers.
This section also explains the bigger design benefit. If the framework later needs to change how URLs are built, how authentication is handled, how logging works, or how status codes are verified, those changes can happen inside the client instead of being repeated across many tests.
By the end of this section, the existing API tests will be updated to use a reusable client layer, giving us a cleaner foundation for the next round of endpoint automation.
In this lecture, we continue improving the framework by creating an API client for reusable `GET` requests.
So far, each test has been calling `requests.get()` directly, passing headers manually, parsing the response, and checking status codes inside the test itself. That works for a few tests, but it becomes repetitive as the suite grows.
You will create a `clients` folder and an `api_client.py` file, then define an `APIClient` class with a reusable `get()` method. The client starts by wrapping `requests.get()`, then takes over common behavior like asserting the expected `200` status code and returning the response. We also add a `get_json()` helper for the most common case where the test only needs the JSON response body.
After building the client, we update existing protected endpoint tests to use the API client instead of direct `requests` calls. We run the tests, troubleshoot a Python class error caused by a missing `self`, fix it, and confirm the updated tests pass.
By the end, the framework has the start of a reusable API client layer that will make future endpoint tests cleaner and easier to maintain.
In this lecture, we continue building the reusable API client by adding support for `POST` requests.
The client already handles `GET` and `get_json()` behavior. Now we add matching `post()` and `post_json()` methods so login and other POST-based API calls can use the same client layer instead of calling `requests.post()` directly.
You will update the login test and authentication helper to use the API client, then handle the framework design issues that show up along the way. We add support for request payloads, make headers optional for endpoints like login that do not require authentication, and make the expected status code configurable so negative tests can validate responses like `401` instead of always expecting `200`.
We also debug real failures from the refactor, including unexpected keyword arguments, response-object versus JSON-body mistakes, and missing optional defaults. After fixing those issues, we run the full test suite and confirm all tests pass.
By the end, the API client supports both `GET` and `POST` patterns and is flexible enough for positive and negative API tests.
In this lecture, we make a small but important cleanup inside the API client by centralizing status code verification.
The client already supports multiple request methods, and each method is starting to repeat the same status code assertion and error-message logic. Instead of duplicating that check in every `GET`, `POST`, and future `PATCH` or `DELETE` method, we create one reusable `verify_status_code()` helper inside the client.
You will see how the helper accepts the response object, the expected status code, and the URL being called. This lets the framework produce clearer assertion failures that show both the expected and actual status code, plus the endpoint that failed.
We then update the existing `get()` and `post()` methods to call the shared helper and make `expected_status_code` configurable with a default of `200`. That keeps the common happy path simple while still supporting negative tests or endpoints that should return other status codes.
By the end, the API client is cleaner, easier to extend, and better prepared for additional request methods and more detailed reporting.
In this lecture, we continue improving the API client by moving base URL handling out of individual tests and into the client itself.
Up to this point, tests have been building full URLs by combining the base URL with each endpoint before passing them into the API client. That works, but it spreads URL-building logic across the test suite and makes future environment changes harder to manage.
You will add a `build_url()` method to the `APIClient` so tests only pass the endpoint path, such as `/applications` or `/auth/login`. The client becomes responsible for combining the configured base URL with the endpoint, which makes tests cleaner and keeps environment-specific logic in one place.
We also refactor existing login, helper, and protected endpoint tests to pass endpoints instead of full URLs. Along the way, we debug a real issue where the base URL gets applied more than once because helper methods are calling other client methods. This shows why it matters to understand the call flow inside your framework.
By the end, URL construction is centralized in the API client, the tests are cleaner, and the full test suite passes after the refactor.
In this lecture, we add useful logging to the API testing framework and configure Pytest so those logs show up consistently.
Before adding logging, the tests may pass or fail without showing much about which API calls were made or what happened during the request flow. We start by exploring Pytest logging options from the command line, including `log_cli` and `log_cli_level`, and compare noisy `DEBUG` output with cleaner `INFO` output.
Then we create a `pytest.ini` file so logging behavior is configured once instead of repeated in every terminal command. After that, we add Python logging to the API client so request activity, authentication setup, and status-code checks can produce useful messages without overloading the test output.
You will also see the practical tradeoff: too little logging makes debugging harder, but too much logging can create noisy test reports and unnecessary CI/CD log volume. We keep the framework focused on helpful messages and leave deeper debug output available when needed.
By the end, the framework has cleaner observability, better test output, and a reusable Pytest logging configuration.
In this lecture, we finish the API client cleanup by adding docstrings and committing the framework improvements.
After several refactors, the API client, helpers, and tests are working, but the code also needs clear documentation so future maintainers can understand what each file, class, and method is responsible for. We use AI as a practical documentation assistant to generate Google-style docstrings, then review the output before accepting it.
You will see where docstrings can be added at the file, class, method, and test levels. The focus is not on over-documenting every line of code, but on adding enough context so the framework is easier to read and can later support generated documentation if needed.
Once the documentation is reviewed, we check Git status, create a feature branch, commit the API client changes, push the branch, open a pull request, review the changed files, check for mistakes like forgotten breakpoints, and merge the PR.
By the end, the API client refactor is documented, committed, reviewed, and ready to use for the next set of API tests.
In this section intro, we move into more in-depth API tests that focus on real testing mindset, not just framework structure.
The goal of this section is to write more realistic smoke tests against useful endpoints while making only small framework changes when needed. Instead of simply sending one request and checking one response, we begin thinking through fuller test scenarios: what should be validated, what data needs to be created, what data needs to be cleaned up, and how to avoid corrupting the test environment.
This section also introduces tests that may require a chain of API calls. One test case might create data, call another endpoint, validate the result, and then clean up after itself. That is closer to how real API automation works on a team.
The lecture also reinforces an important learning point: students should not watch these videos passively. To actually learn API test automation, they need to type the code, build their own framework, and use AI as assistance instead of only watching someone else work.
By the end, students know this section is about deeper test design, better validation thinking, cleaner data handling, and more realistic API test flows.
In this lecture, we start building a new test for one of the main features of the Job Tracker API: creating a job application.
We begin by exploring the `POST /applications` endpoint in Swagger, checking that it requires authentication, reviewing the request schema, and identifying the required fields such as `company_name` and `role`. We also look at enum-style fields like `remote_type` so the test data uses valid values.
Then we connect the API behavior back to the real product flow. When a user fills out the “new application” form in the UI and saves it, this is the API call that creates the application record. That context helps us understand why this endpoint deserves a stronger regression test.
You will create a new Pytest file, build a realistic payload, use the reusable `APIClient` to make an authenticated `POST` call, and set the expected status code to `201` because this endpoint creates a new resource. After the response comes back, we verify the generated `id` is an integer and confirm that the response contains the same values we sent in the request payload.
By the end, the first part of the create-application test is working: the API call succeeds, the response body is validated, and the test is ready for the next step—fetching the created application to prove it was actually saved.
In this lecture, we continue the create-application test by adding a `GET` call after the `POST` request.
In the previous part, the API returned a successful `201 Created` response and echoed back the application data. That is important, but it does not fully prove the record was actually saved. To strengthen the test, we take the `id` from the POST response and use it to call the get-by-id endpoint.
You will build the `GET /applications/{application_id}` endpoint dynamically, call it with the reusable `APIClient`, inspect the returned JSON, and compare the fetched application data against the original payload. This gives the test a stronger validation flow: create the application, fetch the same application, and confirm the persisted data matches what we sent.
We also discuss why this pattern matters in real API testing. A create endpoint should not only return a good-looking response; the data should be retrievable afterward. This makes the test closer to a real regression test and prepares us for the final step: cleaning up the test data we created.
By the end, the test verifies both the POST response and the follow-up GET response for the newly created application.
In this lecture, we finish the create-application test by cleaning up the test data created during the run.
Automation tests are meant to run often, sometimes many times per day. If a test creates a new application record every time it runs and never removes it, the database quickly fills with unnecessary test data. To avoid that, we add a cleanup step that deletes the application after the POST and GET validations are complete.
You will inspect the `DELETE /applications/{application_id}` endpoint in Swagger, confirm that a successful delete returns `204 No Content`, and add a reusable `delete()` method to the `APIClient`. Since this endpoint does not return a JSON body, the client returns the raw response object and relies on status-code verification instead of trying to parse JSON.
We also discuss the tradeoff between simple inline cleanup and a more formal teardown using Pytest fixtures. For now, we keep the test easy to understand by deleting the record at the end of the test. Later, the framework can evolve toward proper setup/teardown patterns.
By the end, the create-application test creates an application, verifies the POST response, fetches the created record with GET, and deletes the test data so the environment stays cleaner.
In this lecture, we write an end-to-end API test for updating an application status with a PATCH request.
We start by looking at the update application endpoint and choosing a focused scenario: change an application from one status to another, such as from applied to in progress. This keeps the test clear and gives us a realistic workflow that users perform in the job tracker app.
The test design follows a full setup, action, validation, and cleanup flow. First, we create a new application so the test owns its own data instead of randomly modifying existing records. Then we call the PATCH endpoint to update the status. After that, we validate the response and make a follow-up GET request to confirm the updated status was actually saved. Finally, we delete the application so the test does not leave data behind.
Along the way, we also extend the API client by adding PATCH and PATCH JSON helper methods. A debugging moment shows why the request failed with a 422 response: the PATCH request was missing its update payload. We improve the framework error message so failed status-code checks include the response body, making future API debugging easier.
By the end, students have a realistic update test that creates its own data, updates it, verifies persistence, and cleans up after itself.
In this lecture, we write a dedicated smoke test for deleting a job application through the API.
Instead of deleting a random existing record, we create a new application as setup data, capture its generated `id`, delete that same application, and then verify the result. This keeps the test safer because it only deletes data that the test created.
You will build a new Pytest file for the delete workflow, reuse the `APIClient`, call the `DELETE /applications/{application_id}` endpoint, and confirm the successful delete response. Then we make a follow-up `GET /applications/{application_id}` call and expect `404 Not Found`, which tells us the deleted application is no longer retrievable through the API.
Along the way, we also catch and fix an API client bug: the `delete()` method was making the request but not calling the shared status-code verification method. Fixing that now prevents future negative delete tests from silently passing when they should fail.
By the end, you have a clean delete smoke test that creates its own test data, deletes it, verifies the delete behavior with a follow-up GET, and sets up the next topic: using SQL/database checks for deeper API validation.
In this section intro, we shift from API-only validation into SQL-backed API testing.
APIs usually read from and write to a database, but the API response does not always expose everything we need to verify. Sometimes we can validate behavior through another API call, but other times the only reliable source of truth is the database itself.
In this section, you will see why SQL matters for API and backend testing. We use SQL for two major purposes: validating that an API really changed the data correctly, and preparing specific data conditions needed for future test scenarios.
The Job Tracker API also includes audit-style logging in the database when applications are created, updated, or deleted. Since that audit data is not exposed directly through the API, we will connect to the database and query it ourselves. We start with SQLite because it is lightweight and easy to inspect, then we evolve the automation framework so code can connect to the database and fetch validation data.
By the end of this section, students will understand how SQL fits into API testing, how database checks strengthen backend validation, and how a SQL/database client can become part of a professional automation framework.
In this lecture, we look at how to manually open and inspect the SQLite database used by the Job Tracker API before we automate database checks in Python.
SQLite is a lightweight database stored as a single file, which makes it easy to explore locally. We locate the app’s database file under the backend data folder and discuss how SQLite differs from server-based databases like MySQL or PostgreSQL.
You will see two practical ways to read the database: the SQLite extension for VS Code/Cursor by `alexcvzz`, and the standalone `DB Browser for SQLite` desktop app. We open the database, browse tables, inspect rows, and run a simple `SELECT` query from the IDE extension.
This lesson also explains basic database structure for beginners: a database contains tables, and tables are similar to sheets in Excel or Google Sheets. In this app, tables include users, job applications, API keys, and audit logs.
By the end, students know how to manually inspect the SQLite database, run simple read queries, and choose a tool they can use while building SQL-backed API tests.
In this lecture, we start a SQL-backed API testing scenario by manually verifying that application changes are written to the audit log table.
The lesson explains when SQL validation is useful in API testing. Most of the time, if an API exists to fetch the result, we should prefer using the API because database queries can be expensive and risky. But some important backend behavior is not exposed through Swagger or any public endpoint. Audit logs are a good example.
We inspect the application audit log table and discuss why audit data matters in real companies. Status changes, user activity, IP addresses, old values, and new values may be needed for legal, security, accounting, or compliance reasons. If the application appears to work but the audit table is not updated correctly, the business can still have a serious problem.
Then we manually test the behavior. We use Swagger to update an application status and use a SQLite database viewer to confirm that a new row appears in the audit log table. The row shows the application ID, action, old status, new status, and related audit data.
After that, we begin creating an automated test file for audit log validation. The first step creates an application through the API, then we manually run SQL to confirm the audit log row exists. This is intentionally half automated and half manual so students can see the behavior before coding the SQL verification.
By the end, students understand the test case: create or update an application through the API, then verify the hidden audit-log behavior directly in the database.
In this lecture, we add SQL directly into an API test so we can validate data that is not exposed through the API response.
The test creates an application through the API, but the audit log table is only available in the database. To verify that the audit record was created, we use Python’s built-in `sqlite3` module to connect to the SQLite database, run a query, fetch the matching audit log row, and assert that the database data matches the application we created.
You will see how to create a SQLite connection, set `row_factory` so rows can be accessed by column name, create a database cursor, write a SQL query, filter by `application_id`, call `fetchone()`, and close both the cursor and connection after reading from the database.
We also talk through important framework design tradeoffs. Hard-coding a full database path is useful for a quick proof of concept, but it will not work across different machines or CI/CD environments. This pain is intentional: once we see the repeated connection code and hard-coded path problem, the need for a reusable SQL client becomes obvious.
By the end, the API test can validate the hidden audit log database record, and we are ready to refactor the database logic into a cleaner SQL client.
In this lecture, we refactor direct SQLite code into a reusable SQL client for the API testing framework.
The previous SQL test connected to the database and ran the query directly inside the test. That worked for one example, but it would become messy if many tests needed database validation. Instead, we create a small `SQLClient` class that handles database connection, query execution, and returning results in a cleaner format.
We start with the basic client structure and add an `execute_query()` method. The client connects to SQLite, executes the SQL passed in by the test, fetches all matching rows, closes the connection, and returns the data.
The lecture also explains why `fetchall()` is a better default than `fetchone()` for a generic query method. Some tests may expect one row, some may expect many rows, and some may expect zero rows. Returning a list gives the test flexibility.
Then we improve the returned data by converting SQLite row objects into a list of dictionaries. That makes the result easier to read, debug, and assert against because each row uses column names as dictionary keys.
Finally, we move the database path into configuration so tests do not hard-code local file paths. The SQL client can use the default configured path or accept a path when needed, and it raises clear errors when the path is missing or invalid.
By the end, students have a reusable SQL client that makes database-backed API tests cleaner, easier to maintain, and easier to extend later if the database changes.
In this lecture, we add another SQL-backed API test: verifying that updating an application writes the correct audit log record in the database.
We start by manually walking through the flow in Swagger. First, we create an application so we have test data to update. Then we use the PATCH endpoint to change the application status and inspect the audit log table to confirm a new update record was written.
From there, we automate the same workflow. The test creates an application as setup data, saves the starting status and target status in variables, calls the update endpoint, and then uses the SQL client to query the audit log table for that application ID.
Because one application can have multiple audit log rows, we update the SQL query to order by the audit log ID in descending order and limit the result to one row. That lets us validate the latest audit entry instead of accidentally checking an older create record.
By the end, the test verifies that an application update writes an audit log row with the correct `updated` action, `old_status`, and `new_status`, giving us deeper backend validation than the API response alone can provide.
In this lecture, we add a SQL-backed test that verifies the audit log record written when an application is deleted.
We start by manually walking through the flow: create an application, delete it through the API, confirm the application returns `404 Not Found` when fetched again, and inspect the audit log table to see the new `deleted` row.
Then we automate the same workflow. The test creates an application as setup data, stores the original status, calls the `DELETE /applications/{application_id}` endpoint, and makes a follow-up GET request expecting `404` to confirm the application is gone.
After that, we query the audit log table with the SQL client, fetch the latest audit row for the application, and validate that the action is `deleted`, the `old_status` matches the status before deletion, and the `new_status` is `None` because the application no longer has an active status.
By the end, we have SQL-backed coverage for create, update, and delete audit log behavior, plus more practice combining API actions with database validation.
In this lecture, we write a SQL-backed security test to verify that API keys are not stored in the database as plain text.
The API allows users to create API keys for programmatic access. Because API keys behave like passwords, the application should only show the full key once, and the database should store a hashed version instead of the original raw key value.
You will create a new API key through the API, capture the response values such as the generated key, key prefix, and database ID, then query the `api_keys` table with the SQL client. The test checks that the prefix stored in the database matches the prefix returned by the API, while the stored `hashed_key` does not match the raw API key value.
We also hit a useful framework lesson: changing the default expected status code for all POST calls can accidentally break login, because login returns `200` while create-style POST endpoints return `201`. This reinforces why framework defaults matter and why exceptions should be handled explicitly.
By the end, the test validates an important backend security rule: API keys can be created and used, but the raw secret value is not stored directly in the database.
In this lecture, we wrap up the SQL-focused section by documenting the latest framework changes and committing the work with Git.
This is a checkpoint lesson. Instead of adding a new API test, we pause to clean up the project, add missing docstrings, review the changes, create a pull request, and merge the work. The goal is to show the professional habit of saving progress before moving into the next section.
We use AI assistance to add meaningful docstrings to files, helper methods, and tests that need better explanation. The lecture also reinforces an important rule: AI can save time, but you still need to review what it changed. Letting AI modify code without verification is risky, especially in a real automation framework.
After the documentation updates, we review the pull request changes, check that nothing concerning was added, and merge the work. The lecture also encourages students to keep their own framework work tracked in Git because Git and CI/CD become important later in the course.
By the end, students see that professional test automation is not only about writing tests. It also includes documentation, code review, version control, pull requests, and clean checkpoints before continuing.
In this section intro, we shift into helper methods and why they matter in a real automation framework.
As API tests grow, we start noticing repeated setup code. Many tests need an application record before they can test updates, deletes, database records, UI behavior, or other workflows. Instead of rebuilding the same payload and API call in every test, we create helper functions that handle that setup for us.
This section explains why helpers keep test code cleaner, reduce copy/paste maintenance, and make common actions easier to reuse. We use application setup as the main example: when a test needs an application, it should be able to call a helper and focus on the behavior being tested.
We also connect this to real team workflows. In larger companies, different teams often own different services or features. A team that owns application APIs can provide helper methods so frontend, account, authentication, or other teams can create the data they need without learning every endpoint detail.
By the end of this section, students will understand why helper methods are an important part of professional test automation frameworks and how they support cleaner tests, reusable setup, and better collaboration across teams.
In this lecture, we begin the helper-focused section by creating a reusable helper for application setup data.
Many API tests need an application record before they can test something else, such as updating notes, changing fields, validating delete behavior, or checking database records. Instead of copying the full create-application payload into every test, we move that setup logic into a helper function.
You will create an `application_helper.py` file, add a `create_application()` helper, and move the repeated create-application API call into that helper. The helper returns the full created application JSON so each test can decide whether it needs the `id`, notes, status, remote type, or other fields.
We also separate the payload-building logic into a `build_create_application_payload()` function. That makes the code easier to grow later when tests need different setup data. Finally, we improve the helper design by passing in the `APIClient` from the test instead of creating it inside the helper, giving the test control over authentication and which user is being used.
By the end, the test can create setup data with one clean helper call, and the framework is ready for more helpers around update, get, and cleanup workflows.
In this lecture, we continue improving the helper layer by creating a reusable helper for fetching an application by ID.
After updating an application, we do not want to rely only on the PATCH response. We also want to make a follow-up `GET /applications/{application_id}` call to confirm the saved application data matches what we updated.
You will add a `get_application()` helper to `application_helper.py`. The helper accepts the existing `APIClient` and the application ID, builds the GET endpoint, calls `get_json()`, and returns the application response. This keeps the test cleaner while still letting the test control authentication and user context through the client object.
Then we update the notes-field test to call the helper, verify the fetched application contains the updated `notes` value, and compare it against the update payload. This gives the test multi-layer validation: the update response looks correct, and a separate GET call confirms the change was persisted.
By the end, the test is cleaner, the framework has another reusable application helper, and the next improvement is clear: add a delete helper so the test can clean up the application it created.
In this lecture, we complete the basic application helper workflow by adding a reusable helper for deleting application test data.
The notes update test creates a new application, updates it, and fetches it again for validation. That means the test also needs a cleanup step so the database does not keep every application record created during automation runs.
You will add a `delete_application()` helper to `application_helper.py`. The helper follows the same pattern as the create and get helpers: it accepts the existing `APIClient` and the application ID, builds the application endpoint, and calls the client’s delete method. Since a successful delete returns `204 No Content`, the helper does not need to return JSON.
Then we import the helper into the test and call it at the end, passing the same API client and application ID used earlier in the workflow. This gives the test a clear setup, action, validation, and cleanup structure.
By the end, the application helper file can create, fetch, and delete application records, making future API tests cleaner and easier to maintain.
In this lecture, we start the helper-focused section by writing a cleaner update test for the `remote_type` field.
We begin by reviewing the API schema in Swagger to understand the allowed `remote_type` enum values, such as `remote`, `hybrid`, and `on_site`. This is important because field-specific update tests should be based on the API contract, not random values.
Then we use the existing application helper to create setup data, but improve it so the test can control the initial payload. Instead of always accepting the helper’s default `remote_type`, we add a flexible override pattern using Python keyword arguments and `payload.update()`. That lets each test create application data with exactly the values it needs.
After creating an application with one remote type, the test updates it to another valid value through the PATCH endpoint. We verify the update response, make a follow-up GET request to confirm the persisted value, and delete the application afterward so the test cleans up after itself.
Along the way, we also catch a realistic mistake: sending the wrong JSON field name causes the API to ignore the update instead of changing the record. That becomes a useful discussion point about API validation behavior and why tests should fail clearly when payload keys are wrong.
By the end, students see how helpers make API tests cleaner, more flexible, and easier to reuse across multiple update scenarios.
In this lecture, we add another field-specific update test using the helper methods we built earlier.
This time, the test focuses on updating the `salary_range` field on an application. We reuse the same helper-driven pattern from the previous remote-type test: create an application with known setup data, update one field through the PATCH endpoint, verify the update response, fetch the application again, and clean up the test data afterward.
You will see how helpers make this kind of test much faster to write because the create, get, and delete behavior is already wrapped in reusable functions. The main work becomes choosing the field, setting the before and after values, and making sure the assertions target the correct response keys.
We also hit a realistic copy/paste bug. Because this test was duplicated from the remote-type test, a few `remote_type` references were accidentally left behind. The API returned a clear `422` validation error, which helped us trace the issue and fix the copied field names.
Learn API Testing by Building a Real Automation Framework
Learn API testing and backend automation by building a real Python and PyTest testing framework from the ground up.
This course is a complete rebuild of my API testing course for modern QA automation engineers, SDETs, and testers who want practical backend testing skills. Instead of only sending simple API requests or checking status codes, you will work with a real Job Tracker application, explore its Swagger documentation, run the backend locally, and build automated tests against real API workflows.
What Makes This Course Different
This is not a toy API demo. The course uses a real application with a frontend, backend API, authentication, database storage, Swagger documentation, and realistic workflows.
You will test public endpoints, authenticated endpoints, create/update/delete application workflows, negative scenarios, error responses, and API behavior that needs database validation.
API Testing Fundamentals
You will start with the fundamentals before jumping into automation:
- What APIs are and why applications use them
- How HTTP requests and responses work
- How JSON payloads are sent and returned
- How status codes communicate success and failure
- How Swagger helps you explore API documentation
- How Postman helps you practice API calls before writing code
Python and PyTest Framework
You will build a Python API testing framework step by step using PyTest.
The framework grows naturally as the tests become more realistic. You will create:
- Test files and project structure
- Configuration for different environments
- A reusable API client
- Helper functions for setup and cleanup
- PyTest fixtures
- Markers and test case IDs
- HTML reports and CI-friendly output
- GitHub Actions CI/CD execution
Real API Test Coverage
The course includes practical API automation examples such as:
- Public API endpoint tests
- Authentication tests
- Protected endpoint tests
- Create application workflow tests
- Update application workflow tests
- Delete application workflow tests
- Negative API tests
- Error response validation
- Database-backed API verification
- Security-focused backend checks
SQL, MySQL, and Database Validation
This course also includes a bonus SQL and MySQL tutorial.
SQL is a valuable skill for QA engineers, automation testers, and SDETs because many backend testing jobs require some database understanding. The bonus SQL section gives you practical SQL practice with MySQL, then the API testing sections show how database validation fits into real backend automation work.
You will also learn when API response validation is enough, when database validation is useful, and how to query a local SQLite database from Python to verify backend behavior, audit logs, and security-related data.
Reports and CI/CD
A real automation framework should not only run on your laptop.
You will learn how to generate practical PyTest reports and run your API tests in GitHub Actions CI/CD. By the end, your tests can run locally and in a pipeline, with organized output that is easier to review and share.
AI-Assisted Testing Workflow
Modern testing work increasingly includes AI-assisted workflows, so this course shows practical ways to use AI without turning the course into AI hype.
You will see how AI can help with documentation, test case tracking, boilerplate code, docstrings, repetitive framework work, and code review.
But the core testing decisions stay human. You will learn what to test, how to verify behavior, how to review generated code, and how to run tests to prove the framework works.
Portfolio and Career Value
By the end of the course, you should be able to build and explain a real API testing framework using Python, PyTest, SQL validation, reports, and CI/CD.
You will also have a portfolio-ready API automation project that shows practical backend testing skills employers care about.