Protractor is a popular end-to-end test framework that lets you test your Angular application on a real browser simulating the browser interactions just the way that a real user would interact with it. End-to-end tests are designed to ensure that the application behaves as expected from a user's perspective. Moreover, the tests are not concerned about the actual code implementation.
Protractor runs on top of the popular Selenium WebDriver, which is an API for browser automation and testing. In addition to the features provided by Selenium WebDriver, Protractor offers locators and methods for capturing the UI components of the Angular application.
In this tutorial, you will learn about:
Doesn't that sound exciting? However, first things first.
If you've been using Angular-CLI, you might know that by default, it comes shipped with two frameworks for testing. They are:
The apparent difference between the two is that the former is used to test the logic of the components and services, while the latter is used to ensure that the high-level functionality (which involves the UI elements) of the application works as expected.
If you are new to testing in Angular, I'd recommend reading the Testing Components in Angular Using Jasmine series to get a better idea of where to draw the line.
In the former's case, you can leverage the power of Angular testing utilities and Jasmine to write not just unit tests for components and services, but basic UI tests also. However, if you need to test the front-end functionality of your application from start to end, Protractor is the way to go. Protractor's API combined with design patterns such as page objects make it easier to write tests that are more readable. Here's an example to get things rolling.
/* 1. It should have a create Paste button 2. Clicking the button should bring up a modal window */ it('should have a Create Paste button and modal window', () => { expect(addPastePage.isCreateButtonPresent()).toBeTruthy("The button should exist"); expect(addPastePage.isCreatePasteModalPresent()).toBeFalsy("The modal window shouldn't exist, not yet!"); addPastePage.clickCreateButton(); expect(addPastePage.isCreatePasteModalPresent()).toBeTruthy("The modal window should appear now"); });
Setting up Protractor is easy if you are using Angular-CLI to generate your project. The directory structure created by ng new
is as follows.
. ├── e2e │ ├── app.e2e-spec.ts │ ├── app.po.ts │ └── tsconfig.e2e.json ├── karma.conf.js ├── package.json ├── package-lock.json ├── protractor.conf.js ├── README.md ├── src │ ├── app │ ├── assets │ ├── environments │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── typings.d.ts ├── tsconfig.json └── tslint.json 5 directories, 19 files
The default project template created by Protractor depends on two files to run the tests: the spec files that reside inside the e2e directory and the configuration file (protractor.conf.js). Let's see how configurable protractor.conf.js is:
/* Path: protractor.conf.ts*/ // Protractor configuration file, see link for more information // https://github.com/angular/protractor/blob/master/lib/config.ts const { SpecReporter } = require('jasmine-spec-reporter'); exports.config = { allScriptsTimeout: 11000, specs: [ './e2e/**/*.e2e-spec.ts' ], capabilities: { 'browserName': 'chrome' }, directConnect: true, baseUrl: 'http://localhost:4200/', framework: 'jasmine', jasmineNodeOpts: { showColors: true, defaultTimeoutInterval: 30000, print: function() {} }, onPrepare() { require('ts-node').register({ project: 'e2e/tsconfig.e2e.json' }); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); } };
If you are ok with running the test on Chrome web browser, you can leave this as is and skip the rest of this section.
The directConnect: true
lets Protractor connect directly to the browser drivers. However, at the moment of writing this tutorial, Chrome is the only supported browser. If you need multi-browser support or run a browser other than Chrome, you will have to set up Selenium standalone server. The steps are as follows.
Install Protractor globally using npm:
npm install -g protractor
This installs the command-line tool for webdriver-manager along with that of protractor. Now update the webdriver-manager to use the latest binaries, and then start the Selenium standalone server.
webdriver-manager update webdriver-manager start
Finally, set the directConnect: false
and add the seleniumAddress
property as follows:
capabilities: { 'browserName': 'firefox' }, directConnect: false, baseUrl: 'http://localhost:4200/', seleniumAddress: 'http://localhost:4444/wd/hub', framework: 'jasmine', jasmineNodeOpts: { showColors: true, defaultTimeoutInterval: 30000, print: function() {} },
The config file on GitHub provides more information about the configuration options available on Protractor. I will be using the default options for this tutorial.
ng e2e
is the only command you need to start running the tests if you are using Angular-CLI. If the tests appear to be slow, it's because Angular has to compile the code every time you run ng e2e
. If you want to speed it up a bit, here's what you should do. Serve the application using ng serve
.
Then fire up a new console tab and run:
ng e2e -s false
The tests should load faster now.
We will be writing E2E tests for a basic Pastebin application. Clone the project from the GitHub repo.
Both the versions, the starter version (the one without the tests) and the final version (the one with the tests), are available on separate branches. Clone the starter branch for now. Optionally, serve the project and go through the code to get acquainted with the application at hand.
Let's describe our Pastebin application briefly. The application will initially load a list of pastes (retrieved from a mock server) into a table. Each row in the table will have a View Paste button which, when clicked on, opens up a bootstrap modal window. The modal window displays the paste data with options to edit and delete the paste. Towards the end of the table, there is a Create Paste button which can be used to add new pastes.
The rest of the tutorial is dedicated to writing Protractor tests in Angular.
The spec file, ending with .e2e-spec.ts, will host the actual tests for our application. We will be placing all the test specs inside the e2e directory since that's the place we've configured Protractor to look for the specs.
There are two things you need to consider while writing Protractor tests:
Create a new file called test.e2e-spec.ts with the following code to get started.
/* Path: e2e/test.e2e-spec.ts */ import { browser, by, element } from 'protractor'; describe('Protractor Demo', () => { beforeEach(() => { //The code here will get executed before each it block is called //browser.get('/'); }); it('should display the name of the application',() => { /*Expectations accept parameters that will be matched with the real value using Jasmine's matcher functions. eg. toEqual(),toContain(), toBe(), toBeTruthy() etc. */ expect("Pastebin Application").toEqual("Pastebin Application"); }); it('should click the create Paste button',() => { //spec goes here }); });
This depicts how our tests will be organized inside the spec file using Jasmine's syntax. describe()
, beforeEach()
and it()
are global Jasmine functions.
Jasmine has a great syntax for writing tests, and it works just as well with Protractor. If you are new to Jasmine, I would recommend going through Jasmine's GitHub page first.
The describe block is used to divide the tests into logical test suites. Each describe block (or test suite) can have multiple it blocks (or test specs). The actual tests are defined inside the test specs.
"Why should I structure my tests this way?" you may ask. A test suite can be used to logically describe a particular feature of your application. For instance, all the specs concerned with the Pastebin component should ideally be covered inside a describe block titled Pastebin Page. Although this may result in tests that are redundant, your tests will be more readable and maintainable.
A describe block can have a beforeEach()
method which will be executed once, before each spec in that block. So, if you need the browser to navigate to a URL before each test, placing the code for navigation inside beforeEach()
is the right thing to do.
Expect statements, which accept a value, are chained with some matcher functions. Both the real and the expected values are compared, and a boolean is returned which determines whether the test fails or not.
Now, let's put some flesh on it.
/* Path: e2e/test.e2e-spec.ts */ import { browser, by, element } from 'protractor'; describe('Protractor Demo', () => { beforeEach(() => { browser.get('/'); }); it('should display the name of the application',() => { expect(element(by.css('.pastebin')).getText()).toContain('Pastebin Application'); }); it('create Paste button should work',() => { expect(element(by.id('source-modal')).isPresent()).toBeFalsy("The modal window shouldn't appear right now "); element(by.buttonText('create Paste')).click(); expect(element(by.id('source-modal')).isPresent()).toBeTruthy('The modal window should appear now'); }); });
browser.get('/')
and element(by.css('.pastebin')).getText()
are part of the Protractor API. Let's get our hands dirty and jump right into what Protractor has to offer.
The prominent components exported by Protractor API are listed below.
browser()
: You should call browser()
for all the browser-level operations such as navigation, debugging, etc. element()
: This is used to look up an element in the DOM based on a search condition or a chain of conditions. It returns an ElementFinder object, and you can perform actions such as getText()
or click()
on them.element.all()
: This is used to look for an array of elements that match some chain of conditions. It returns an ElementArrayFinder object. All the actions that can be performed on ElementFinder can be performed on ElementArrayFinder also.Since we will be using locators very often, here are some of the commonly used locators.
by.css('selector-name')
: This is by far the commonly used locator for finding an element based on the name of the CSS selector.by.name('name-value')
: Locates an element with a matching value for the name attribute.by.buttonText('button-value')
: Locates a button element or an array of button elements based on the inner text. Note: The locators by.model, by.binding and by.repeater do not work with Angular 2+ applications at the time of writing this tutorial. Use the CSS-based locators instead.
Let's write more tests for our Pastebin application.
it('should accept and save input values', () => { element(by.buttonText('create Paste')).click(); //send input values to the form using sendKeys element(by.name('title')).sendKeys('Hello world in Ruby'); element(by.name('language')).element(by.cssContainingText('option', 'Ruby')).click(); element(by.name('paste')).sendKeys("puts 'Hello world';"); element(by.buttonText('Save')).click(); //expect the table to contain the new paste const lastRow = element.all(by.tagName('tr')).last(); expect(lastRow.getText()).toContain("Hello world in Ruby"); });
The code above works, and you can verify that yourself. However, wouldn't you feel more comfortable writing tests without the Protractor-specific vocabulary in your spec file? Here's what I am talking about:
it('should have an Create Paste button and modal window', () => { expect(addPastePage.isCreateButtonPresent()).toBeTruthy("The button should exist"); expect(addPastePage.isCreatePasteModalPresent()).toBeFalsy("The modal window shouldn't appear, not yet!"); addPastePage.clickCreateButton(); expect(addPastePage.isCreatePasteModalPresent()).toBeTruthy("The modal window should appear now"); }); it('should accept and save input values', () => { addPastePage.clickCreateButton(); //Input field should be empty initially const emptyInputValues = ["","",""]; expect(addPastePage.getInputPasteValues()).toEqual(emptyInputValues); //Now update the input fields addPastePage.addNewPaste(); addPastePage.clickSaveButton(); expect(addPastePage.isCreatePasteModalPresent()).toBeFalsy("The modal window should be gone"); expect(mainPage.getLastRowData()).toContain("Hello World in Ruby"); });
The specs appear more straightforward without the extra Protractor baggage. How did I do that? Let me introduce you to Page Objects.
Page Object is a design pattern which is popular in the test automation circles. A page object models a page or part of an application using an object-oriented class. All the objects (that are relevant to our tests) like text, headings, tables, buttons, and links can be captured in a page object. We can then import these page objects into the spec file and invoke their methods. This reduces code duplication and makes maintenance of code easier.
Create a directory named page-objects and add a new file inside it called pastebin.po.ts. All the objects concerned with the Pastebin component will be captured here. As previously mentioned, we divided the whole app into three different components, and each component will have a page object dedicated to it. The naming scheme .po.ts is purely conventional, and you can name it anything you want.
Here is a blueprint of the page we are testing.
Here is the code.
/* Path e2e/page-objects/pastebin.po.ts*/ import { browser, by, element, promise, ElementFinder, ElementArrayFinder } from 'protractor'; export class Pastebin extends Base { navigateToHome():promise.Promise<any> { return browser.get('/'); } getPastebin():ElementFinder { return element(by.css('.pastebin')); } /* Pastebin Heading */ getPastebinHeading(): promise.Promise<string> { return this.getPastebin().element(by.css("h2")).getText(); } /*Table Data */ getTable():ElementFinder { return this.getTable().element(by.css('table')); } getTableHeader(): promise.Promise<string> { return this.getPastebin().all(by.tagName('tr')).get(0).getText(); } getTableRow(): ElementArrayFinder { return this.getPastebin().all(by.tagName('tr')); } getFirstRowData(): promise.Promise<string> { return this.getTableRow().get(1).getText(); } getLastRowData(): promise.Promise<string> { return this.getTableRow().last().getText(); } /*app-add-paste tag*/ getAddPasteTag(): ElementFinder { return this.getPastebin().element(by.tagName('app-add-paste')); } isAddPasteTagPresent(): promise.Promise<boolean> { return this.getAddPasteTag().isPresent(); } }
Let's go over what we've learned thus far. Protractor's API returns objects, and we've encountered three types of objects thus far. They are:
In short, element()
returns an ElementFinder, and element().all
returns an ElementArrayFinder. You can use the locators (by.css
, by.tagName
, etc.) to find the location of the element in the DOM and pass it to element()
or element.all()
.
ElementFinder and ElementArrayFinder can then be chained with actions, such as isPresent()
, getText()
, click()
, etc. These methods return a promise that gets resolved when that particular action has been completed.
The reason why we don't have a chain of then()
s in our test is because Protractor takes care of it internally. The tests appear to be synchronous even though they are not; therefore, the end result is a linear coding experience. However, I recommend using async/await syntax to ensure that the code is future proof.
You can chain multiple ElementFinder
objects, as shown below. This is particularly helpful if the DOM has multiple selectors of the same name and we need to capture the right one.
getTable():ElementFinder { return this.getPastebin().element(by.css('table')); }
Now that we have the code for the page object ready, let's import it into our spec. Here's the code for our initial tests.
/* Path: e2e/mainPage.e2e-spec.ts */ import { Pastebin } from './page-objects/pastebin.po'; import { browser, protractor } from 'protractor'; /* Scenarios to be Tested 1. Pastebin Page should display a heading with text Pastebin Application 2. It should have a table header 3. The table should have rows 4. app-add-paste tag should exist */ describe('Pastebin Page', () => { const mainPage: Pastebin = new Pastebin(); beforeEach(() => { mainPage.navigateToHome(); }); it('should display the heading Pastebin Application', () => { expect(mainPage.getPastebinHeading()).toEqual("Pastebin Application"); }); it('should have a table header', () => { expect(mainPage.getTableHeader()).toContain("id Title Language Code"); }) it('table should have at least one row', () => { expect(mainPage.getFirstRowData()).toContain("Hello world"); }) it('should have the app-add-paste tag', () => { expect(mainPage.isAddPasteTagPresent()).toBeTruthy(); }) });
Tests should be organized in such a way that the overall structure appears meaningful and straightforward. Here are some opinionated guidelines that you should keep in mind while organizing E2E tests.
navigateToHome()
), create a base page object. Other page models can inherit from the base page model. Following the guidelines above, here's what the page object hierarchy and the file organization should look like.
We've already covered pastebin.po.ts and mainPage.e2e-spec.ts. Here are the rest of the files.
/* path: e2e/page-objects/base.po.ts */ import { browser, by, element, promise, ElementFinder, ElementArrayFinder } from 'protractor'; export class Base { /* Navigational methods */ navigateToHome():promise.Promise<any> { return browser.get('/'); } navigateToAbout():promise.Promise<any> { return browser.get('/about'); } navigateToContact():promise.Promise<any> { return browser.get('/contact'); } /* Mock data for creating a new Paste and editing existing paste */ getMockPaste(): any { let paste: any = { title: "Something here",language: "Ruby",paste: "Test"} return paste; } getEditedMockPaste(): any { let paste: any = { title: "Paste 2", language: "JavaScript", paste: "Test2" } return paste; } /* Methods shared by addPaste and viewPaste */ getInputTitle():ElementFinder { return element(by.name("title")); } getInputLanguage(): ElementFinder { return element(by.name("language")); } getInputPaste(): ElementFinder { return element(by.name("paste")); } }
/* Path: e2e/page-objects/add-paste.po.ts */ import { browser, by, element, promise, ElementFinder, ElementArrayFinder } from 'protractor'; import { Base } from './base.po'; export class AddPaste extends Base { getAddPaste():ElementFinder { return element(by.tagName('app-add-paste')); } /* Create Paste button */ getCreateButton(): ElementFinder { return this.getAddPaste().element(by.buttonText("create Paste")); } isCreateButtonPresent() : promise.Promise<boolean> { return this.getCreateButton().isPresent(); } clickCreateButton(): promise.Promise<void> { return this.getCreateButton().click(); } /*Create Paste Modal */ getCreatePasteModal(): ElementFinder { return this.getAddPaste().element(by.id("source-modal")); } isCreatePasteModalPresent() : promise.Promise<boolean> { return this.getCreatePasteModal().isPresent(); } /*Save button */ getSaveButton(): ElementFinder { return this.getAddPaste().element(by.buttonText("Save")); } clickSaveButton():promise.Promise<void> { return this.getSaveButton().click(); } /*Close button */ getCloseButton(): ElementFinder { return this.getAddPaste().element(by.buttonText("Close")); } clickCloseButton():promise.Promise<void> { return this.getCloseButton().click(); } /* Get Input Paste values from the Modal window */ getInputPasteValues(): Promise<string[]> { let inputTitle, inputLanguage, inputPaste; // Return the input values after the promise is resolved // Note that this.getInputTitle().getText doesn't work // Use getAttribute('value') instead return Promise.all([this.getInputTitle().getAttribute("value"), this.getInputLanguage().getAttribute("value"), this.getInputPaste().getAttribute("value")]) .then( (values) => { return values; }); } /* Add a new Paste */ addNewPaste():any { let newPaste: any = this.getMockPaste(); //Send input values this.getInputTitle().sendKeys(newPaste.title); this.getInputLanguage() .element(by.cssContainingText('option', newPaste.language)).click(); this.getInputPaste().sendKeys(newPaste.paste); //Convert the paste object into an array return Object.keys(newPaste).map(key => newPaste[key]); } }
/* Path: e2e/addNewPaste.e2e-spec.ts */ import { Pastebin } from './page-objects/pastebin.po'; import { AddPaste } from './page-objects/add-paste.po'; import { browser, protractor } from 'protractor'; /* Scenarios to be Tested 1. AddPaste Page should have a button when clicked on should present a modal window 2. The modal window should accept the new values and save them 4. The saved data should appear in the MainPage 3. Close button should work */ describe('Add-New-Paste page', () => { const addPastePage: AddPaste = new AddPaste(); const mainPage: Pastebin = new Pastebin(); beforeEach(() => { addPastePage.navigateToHome(); }); it('should have an Create Paste button and modal window', () => { expect(addPastePage.isCreateButtonPresent()).toBeTruthy("The button should exist"); expect(addPastePage.isCreatePasteModalPresent()).toBeFalsy("The modal window shouldn't appear, not yet!"); addPastePage.clickCreateButton(); expect(addPastePage.isCreatePasteModalPresent()).toBeTruthy("The modal window should appear now"); }); it("should accept and save input values", () => { addPastePage.clickCreateButton(); const emptyInputValues = ["","",""]; expect(addPastePage.getInputPasteValues()).toEqual(emptyInputValues); const newInputValues = addPastePage.addNewPaste(); expect(addPastePage.getInputPasteValues()).toEqual(newInputValues); addPastePage.clickSaveButton(); expect(addPastePage.isCreatePasteModalPresent()).toBeFalsy("The modal window should be gone"); expect(mainPage.getLastRowData()).toContain("Something here"); }); it("close button should work", () => { addPastePage.clickCreateButton(); addPastePage.clickCloseButton(); expect(addPastePage.isCreatePasteModalPresent()).toBeFalsy("The modal window should be gone"); }); });
There are a couple of things missing, though: the tests for the View Paste button and the modal window that pops up after clicking the button. I am going to leave this as an exercise for you. However, I will drop you a hint.
The structure of the page objects and the specs for the ViewPastePage are similar to that of the AddPastePage.
Here are the scenarios that you need to test:
Try to stick to the guidelines wherever possible. If you're in doubt, switch to the final branch to see the final draft of the code.
So there you have it. In this article, we've covered writing end-to-end tests for our Angular application using Protractor. We started off with a discussion about unit tests vs. e2e tests, and then we learned about setting up, configuring and running Protractor. The rest of the tutorial concentrated on writing actual tests for the demo Pastebin application.
Please let me know your thoughts and experiences about writing tests using Protractor or writing tests for Angular in general. I would love to hear them. Thanks for reading!
The Best Small Business Web Designs by DesignRush
/Create Modern Vue Apps Using Create-Vue and Vite
/Pros and Cons of Using WordPress
/How to Fix the “There Has Been a Critical Error in Your Website” Error in WordPress
/How To Fix The “There Has Been A Critical Error in Your Website” Error in WordPress
/How to Create a Privacy Policy Page in WordPress
/How Long Does It Take to Learn JavaScript?
/The Best Way to Deep Copy an Object in JavaScript
/Adding and Removing Elements From Arrays in JavaScript
/Create a JavaScript AJAX Post Request: With and Without jQuery
/5 Real-Life Uses for the JavaScript reduce() Method
/How to Enable or Disable a Button With JavaScript: jQuery vs. Vanilla
/How to Enable or Disable a Button With JavaScript: jQuery vs Vanilla
/Confirm Yes or No With JavaScript
/How to Change the URL in JavaScript: Redirecting
/15+ Best WordPress Twitter Widgets
/27 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/21 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/30 HTML Best Practices for Beginners
/31 Best WordPress Calendar Plugins and Widgets (With 5 Free Plugins)
/25 Ridiculously Impressive HTML5 Canvas Experiments
/How to Implement Email Verification for New Members
/How to Create a Simple Web-Based Chat Application
/30 Popular WordPress User Interface Elements
/Top 18 Best Practices for Writing Super Readable Code
/Best Affiliate WooCommerce Plugins Compared
/18 Best WordPress Star Rating Plugins
/10+ Best WordPress Twitter Widgets
/20+ Best WordPress Booking and Reservation Plugins
/Working With Tables in React: Part Two
/Best CSS Animations and Effects on CodeCanyon
/30 CSS Best Practices for Beginners
/How to Create a Custom WordPress Plugin From Scratch
/10 Best Responsive HTML5 Sliders for Images and Text… and 3 Free Options
/16 Best Tab and Accordion Widget Plugins for WordPress
/18 Best WordPress Membership Plugins and 5 Free Plugins
/25 Best WooCommerce Plugins for Products, Pricing, Payments and More
/10 Best WordPress Twitter Widgets
1 /12 Best Contact Form PHP Scripts for 2020
/20 Popular WordPress User Interface Elements
/10 Best WordPress Star Rating Plugins
/12 Best CSS Animations on CodeCanyon
/12 Best WordPress Booking and Reservation Plugins
/12 Elegant CSS Pricing Tables for Your Latest Web Project
/24 Best WordPress Form Plugins for 2020
/14 Best PHP Event Calendar and Booking Scripts
/Create a Blog for Each Category or Department in Your WooCommerce Store
/8 Best WordPress Booking and Reservation Plugins
/Best Exit Popups for WordPress Compared
/Best Exit Popups for WordPress Compared
/11 Best Tab & Accordion WordPress Widgets & Plugins
/12 Best Tab & Accordion WordPress Widgets & Plugins
1 /New Course: Practical React Fundamentals
/Preview Our New Course on Angular Material
/Build Your Own CAPTCHA and Contact Form in PHP
/Object-Oriented PHP With Classes and Objects
/Best Practices for ARIA Implementation
/Accessible Apps: Barriers to Access and Getting Started With Accessibility
/Dramatically Speed Up Your React Front-End App Using Lazy Loading
/15 Best Modern JavaScript Admin Templates for React, Angular, and Vue.js
/15 Best Modern JavaScript Admin Templates for React, Angular and Vue.js
/19 Best JavaScript Admin Templates for React, Angular, and Vue.js
/New Course: Build an App With JavaScript and the MEAN Stack
/Hands-on With ARIA: Accessibility Recipes for Web Apps
/10 Best WordPress Facebook Widgets
13 /Hands-on With ARIA: Accessibility for eCommerce
/New eBooks Available for Subscribers
/Hands-on With ARIA: Homepage Elements and Standard Navigation
/Site Accessibility: Getting Started With ARIA
/How Secure Are Your JavaScript Open-Source Dependencies?
/New Course: Secure Your WordPress Site With SSL
/Testing Components in React Using Jest and Enzyme
/Testing Components in React Using Jest: The Basics
/15 Best PHP Event Calendar and Booking Scripts
/Create Interactive Gradient Animations Using Granim.js
/How to Build Complex, Large-Scale Vue.js Apps With Vuex
1 /Examples of Dependency Injection in PHP With Symfony Components
/Set Up Routing in PHP Applications Using the Symfony Routing Component
1 /A Beginner’s Guide to Regular Expressions in JavaScript
/Introduction to Popmotion: Custom Animation Scrubber
/Introduction to Popmotion: Pointers and Physics
/New Course: Connect to a Database With Laravel’s Eloquent ORM
/How to Create a Custom Settings Panel in WooCommerce
/Building the DOM faster: speculative parsing, async, defer and preload
1 /20 Useful PHP Scripts Available on CodeCanyon
3 /How to Find and Fix Poor Page Load Times With Raygun
/Introduction to the Stimulus Framework
/Single-Page React Applications With the React-Router and React-Transition-Group Modules
12 Best Contact Form PHP Scripts
1 /Getting Started With the Mojs Animation Library: The ShapeSwirl and Stagger Modules
/Getting Started With the Mojs Animation Library: The Shape Module
/Getting Started With the Mojs Animation Library: The HTML Module
/Project Management Considerations for Your WordPress Project
/8 Things That Make Jest the Best React Testing Framework
/Creating an Image Editor Using CamanJS: Layers, Blend Modes, and Events
/New Short Course: Code a Front-End App With GraphQL and React
/Creating an Image Editor Using CamanJS: Applying Basic Filters
/Creating an Image Editor Using CamanJS: Creating Custom Filters and Blend Modes
/Modern Web Scraping With BeautifulSoup and Selenium
/Challenge: Create a To-Do List in React
1 /Deploy PHP Web Applications Using Laravel Forge
/Getting Started With the Mojs Animation Library: The Burst Module
/10 Things Men Can Do to Support Women in Tech
/A Gentle Introduction to Higher-Order Components in React: Best Practices
/Challenge: Build a React Component
/A Gentle Introduction to HOC in React: Learn by Example
/A Gentle Introduction to Higher-Order Components in React
/Creating Pretty Popup Messages Using SweetAlert2
/Creating Stylish and Responsive Progress Bars Using ProgressBar.js
/18 Best Contact Form PHP Scripts for 2022
/How to Make a Real-Time Sports Application Using Node.js
/Creating a Blogging App Using Angular & MongoDB: Delete Post
/Set Up an OAuth2 Server Using Passport in Laravel
/Creating a Blogging App Using Angular & MongoDB: Edit Post
/Creating a Blogging App Using Angular & MongoDB: Add Post
/Introduction to Mocking in Python
/Creating a Blogging App Using Angular & MongoDB: Show Post
/Creating a Blogging App Using Angular & MongoDB: Home
/Creating a Blogging App Using Angular & MongoDB: Login
/Creating Your First Angular App: Implement Routing
/Persisted WordPress Admin Notices: Part 4
/Creating Your First Angular App: Components, Part 2
/Persisted WordPress Admin Notices: Part 3
/Creating Your First Angular App: Components, Part 1
/How Laravel Broadcasting Works
/Persisted WordPress Admin Notices: Part 2
/Create Your First Angular App: Storing and Accessing Data
/Persisted WordPress Admin Notices: Part 1
/Error and Performance Monitoring for Web & Mobile Apps Using Raygun
/Using Luxon for Date and Time in JavaScript
7 /How to Create an Audio Oscillator With the Web Audio API
/How to Cache Using Redis in Django Applications
/20 Essential WordPress Utilities to Manage Your Site
/Introduction to API Calls With React and Axios
/Beginner’s Guide to Angular 4: HTTP
/Rapid Web Deployment for Laravel With GitHub, Linode, and RunCloud.io
/Beginners Guide to Angular 4: Routing
/Beginner’s Guide to Angular 4: Services
/Beginner’s Guide to Angular 4: Components
/Creating a Drop-Down Menu for Mobile Pages
/Introduction to Forms in Angular 4: Writing Custom Form Validators
/10 Best WordPress Booking & Reservation Plugins
/Getting Started With Redux: Connecting Redux With React
/Getting Started With Redux: Learn by Example
/Getting Started With Redux: Why Redux?
/Understanding Recursion With JavaScript
/How to Auto Update WordPress Salts
/How to Download Files in Python
/Eloquent Mutators and Accessors in Laravel
1 /10 Best HTML5 Sliders for Images and Text
/Site Authentication in Node.js: User Signup
/Creating a Task Manager App Using Ionic: Part 2
/Creating a Task Manager App Using Ionic: Part 1
/Introduction to Forms in Angular 4: Reactive Forms
/Introduction to Forms in Angular 4: Template-Driven Forms
/24 Essential WordPress Utilities to Manage Your Site
/25 Essential WordPress Utilities to Manage Your Site
/Get Rid of Bugs Quickly Using BugReplay
1 /Manipulating HTML5 Canvas Using Konva: Part 1, Getting Started
/10 Must-See Easy Digital Downloads Extensions for Your WordPress Site
/22 Best WordPress Booking and Reservation Plugins
/Understanding ExpressJS Routing
/15 Best WordPress Star Rating Plugins
/Creating Your First Angular App: Basics
/Inheritance and Extending Objects With JavaScript
/Introduction to the CSS Grid Layout With Examples
1Performant Animations Using KUTE.js: Part 5, Easing Functions and Attributes
Performant Animations Using KUTE.js: Part 4, Animating Text
/Performant Animations Using KUTE.js: Part 3, Animating SVG
/New Course: Code a Quiz App With Vue.js
/Performant Animations Using KUTE.js: Part 2, Animating CSS Properties
Performant Animations Using KUTE.js: Part 1, Getting Started
/10 Best Responsive HTML5 Sliders for Images and Text (Plus 3 Free Options)
/Single-Page Applications With ngRoute and ngAnimate in AngularJS
/Deferring Tasks in Laravel Using Queues
/Site Authentication in Node.js: User Signup and Login
/Working With Tables in React, Part Two
/Working With Tables in React, Part One
/How to Set Up a Scalable, E-Commerce-Ready WordPress Site Using ClusterCS
/New Course on WordPress Conditional Tags
/TypeScript for Beginners, Part 5: Generics
/Building With Vue.js 2 and Firebase
6 /Best Unique Bootstrap JavaScript Plugins
/Essential JavaScript Libraries and Frameworks You Should Know About
/Vue.js Crash Course: Create a Simple Blog Using Vue.js
/Build a React App With a Laravel RESTful Back End: Part 1, Laravel 5.5 API
/API Authentication With Node.js
/Beginner’s Guide to Angular: Routing
/Beginners Guide to Angular: Routing
/Beginner’s Guide to Angular: Services
/Beginner’s Guide to Angular: Components
/How to Create a Custom Authentication Guard in Laravel
/Learn Computer Science With JavaScript: Part 3, Loops
/Build Web Applications Using Node.js
/Learn Computer Science With JavaScript: Part 4, Functions
/Learn Computer Science With JavaScript: Part 2, Conditionals
/Create Interactive Charts Using Plotly.js, Part 5: Pie and Gauge Charts
/Create Interactive Charts Using Plotly.js, Part 4: Bubble and Dot Charts
Create Interactive Charts Using Plotly.js, Part 3: Bar Charts
/Awesome JavaScript Libraries and Frameworks You Should Know About
/Create Interactive Charts Using Plotly.js, Part 2: Line Charts
/Bulk Import a CSV File Into MongoDB Using Mongoose With Node.js
/Build a To-Do API With Node, Express, and MongoDB
/Getting Started With End-to-End Testing in Angular Using Protractor
/TypeScript for Beginners, Part 4: Classes
/Object-Oriented Programming With JavaScript
/10 Best Affiliate WooCommerce Plugins Compared
/Stateful vs. Stateless Functional Components in React
/Make Your JavaScript Code Robust With Flow
/Build a To-Do API With Node and Restify
/Testing Components in Angular Using Jasmine: Part 2, Services
/Testing Components in Angular Using Jasmine: Part 1
/Creating a Blogging App Using React, Part 6: Tags
/React Crash Course for Beginners, Part 3
/React Crash Course for Beginners, Part 2
/React Crash Course for Beginners, Part 1
/Set Up a React Environment, Part 4
1 /Set Up a React Environment, Part 3
/New Course: Get Started With Phoenix
/Set Up a React Environment, Part 2
/Set Up a React Environment, Part 1
/Command Line Basics and Useful Tricks With the Terminal
/How to Create a Real-Time Feed Using Phoenix and React
/Build a React App With a Laravel Back End: Part 2, React
/Build a React App With a Laravel RESTful Back End: Part 1, Laravel 9 API
/Creating a Blogging App Using React, Part 5: Profile Page
/Pagination in CodeIgniter: The Complete Guide
/JavaScript-Based Animations Using Anime.js, Part 4: Callbacks, Easings, and SVG
/JavaScript-Based Animations Using Anime.js, Part 3: Values, Timeline, and Playback
/Learn to Code With JavaScript: Part 1, The Basics
/10 Elegant CSS Pricing Tables for Your Latest Web Project
/Getting Started With the Flux Architecture in React
/Getting Started With Matter.js: The Composites and Composite Modules
Getting Started With Matter.js: The Engine and World Modules
/10 More Popular HTML5 Projects for You to Use and Study
/Understand the Basics of Laravel Middleware
/Iterating Fast With Django & Heroku
/Creating a Blogging App Using React, Part 4: Update & Delete Posts
/Creating a jQuery Plugin for Long Shadow Design
/How to Register & Use Laravel Service Providers
2 /Unit Testing in React: Shallow vs. Static Testing
/Creating a Blogging App Using React, Part 3: Add & Display Post
/Creating a Blogging App Using React, Part 2: User Sign-Up
20 /Creating a Blogging App Using React, Part 1: User Sign-In
/Creating a Grocery List Manager Using Angular, Part 2: Managing Items
/9 Elegant CSS Pricing Tables for Your Latest Web Project
/Dynamic Page Templates in WordPress, Part 3
/Angular vs. React: 7 Key Features Compared
/Creating a Grocery List Manager Using Angular, Part 1: Add & Display Items
New eBooks Available for Subscribers in June 2017
/Create Interactive Charts Using Plotly.js, Part 1: Getting Started
/The 5 Best IDEs for WordPress Development (And Why)
/33 Popular WordPress User Interface Elements
/New Course: How to Hack Your Own App
/How to Install Yii on Windows or a Mac
/What Is a JavaScript Operator?
/How to Register and Use Laravel Service Providers
/
waly Good blog post. I absolutely love this…