Website Automation for Complete Noobs Part 2

In the first article in the series, we installed Python & Selenium and used them to write a script that opens a browser. In today’s article, we’ll look at how to do some basic inspection of web pages and modify our script to interact with them.

Selenium can do most of the same things you can do with web pages–namely, clicking & typing. But, just like you read and interpret what you see to know what to do, you need to tell your script how to find and do the things you want it to.

The easiest way to do this is to use the “inspect” functionality that exists in most modern browsers to help find identifiers you can use in your code. Let’s try it out with everybody’s ol’ buddy Google. Browse to google.com, right-click the search box, and pick Inspect.

This will take you to the selected element in the web page’s code. This is the part where you start to get some choices. Selenium has a lot of different ways to find elements, so you need to know about what you can use and compare it to what’s available. Usually the most reliable way to get an element is by using its id.

Usually, using an element’s id property along with Selenium’s find_element_by_id method is the most reliable way to find the element, but we don’t see an id when we look at Google’s search box.

We can see a name property with the value q, though, and there’s a find_element_by_name in Selenium, too; so let’s use that! With the element selected, you can use Selenium’s send_keys method to simulate typing. Add the following lines to your script:

from selenium import webdriver

driver = webdriver.Chrome('chromedriver.exe')
driver.get('https://www.google.com')
search = driver.find_element_by_name('q')
search.send_keys('test search')

When you run this script, the browser will open and send the specified text to the search box. Now we need to submit the search. Google has a search button, so we can repeat the steps to find the button and click it:

button = driver.find_element_by_name('btnK')
button.click()

But, that’s not really how you search with Google, right? No–you type your search and press enter. To simulate this, you can use the submit function on the search element:

from selenium import webdriver

driver = webdriver.Chrome('chromedriver.exe')
driver.get('https://www.google.com')
search = driver.find_element_by_name('q')
search.send_keys('test search')
search.submit()

Run the script again, and you’ll see your search submitted and search results displayed. Cool stuff! Let’s do a quick review of all the tools we have now:

  • Browse to sites using webdriver.get
  • Find elements using webdriver.find_element methods
  • Enter text with webdriver.send_keys
  • Click things with webdriver.click
  • Submit forms–like you would do by pressing enter on your keyboard after entering data–with webdriver.submit

Website Automation For Complete Noobs Part 1

Image by mohamed Hassan from Pixabay

Have you ever been doing something on the internet and been like, “Ugh. I wish there was a way to do this automatically.” Well, most of the time there is: you can build some website automation using Python and Selenium. In part 1 of this series, I’ll walk you through installing Python and Selenium, and we’ll write a short script that opens a browser.

Install Python

For folks that don’t know, “Python is a scripting language that lets you work quickly and integrate systems more effectively.” (python.org) That means you write code in files and run the files without having to compile and deploy. In order to run the files, though, you need to install Python on your computer.

One way to do this is to download the installer from the official Python website, here. However, I prefer to use an application called PyCharm. PyCharm is an IDE (integrated development environment) available for Windows, Linux, and Mac that gives you lots of nice helpers when writing scripts, and there’s a very nice free community edition that’s just perfect for people like you who are just getting started.

So that’s step 1 for us today: download and install PyCharm Community Edition. Installation is pretty straightforward; you can just Next through everything and accept the defaults.

Once the install is complete, you can launch PyCharm. The first time you run it, PyCharm will ask you about which theme you prefer. Pick what you like and click Skip Remaining and Set Defaults.

Create Project

PyCharm is ready to go, but we need to do a little more setup before we’re ready to write our code. Begin by creating a new project from the PyCharm launch screen.

Give your project a name to specify where it will live on your computer, and click Create. If you don’t already have Python installed (the actual programming language) at this point, PyCharm will install it for you.

Install Selenium + Chromedriver

Selenium WebDriver is what we’ll use to launch and control the browser in our script. In order to use it, we need to install the libraries. We can install it directly from PyCharm by running a command in the terminal.

Open the terminal by clicking the button.

Now run the following command:

pip install selenium

Selenium also needs an extra application to help control the browser. In this example, I want to use Chrome as my browser, so I need to download ChromeDriver from here. When you visit the ChromeDriver page, it offers a few different versions. You need to pick the version based on the version of Chrome you have installed. This can be found in Chrome’s About page.

Once you determine which version you need, download the Zip file for your OS and extract chromedriver.exe to your project directory. You can verify that it’s in the right spot because it’ll be visible in PyCharm.

Write Script

Flip up those hoodies because it’s time to code. Add a new Python file to your project by right-clicking the project folder.

Add the following code to your script:

from selenium import webdriver

driver = webdriver.Chrome('chromedriver.exe')
driver.get('https://www.google.com')

The first line tells our script that webdriver comes from the Selenium library that we installed. Then, it creates a variable named driver (but we could name it anything) that’s a Selenium WebDriver object using the ChromeDriver executable we downloaded. The third line uses driver to browse to http://www.google.com. Nothing too crazy, right?

Run Script

Go back to your terminal and run your script like this:

python <your-script>.py

When you press enter, a Chrome browser should open and navigate to the URL specified in your script. Chrome also gives an indicator that the browser is being controlled by automation software.

Pretty cool, right? There’s not much to experiment with this script. You can change the URL or add more driver.get lines to visit multiple sites. In the next article in the series, we’ll look at how to use WebDriver to interact with web sites and do things.

Angular & Firebase for Fast, Easy Database Apps

Photo by Campaign Creators on Unsplash

I recently started working on a new personal project that needed a database, and I just wanted something quick & easy. I remembered a colleague mentioning Firebase in the past and decided to take a look. After playing with it for a few days, I really dig it. I love that you can set up a database for free within a few minutes, and access it directly from the frontend without having to introduce a separate API layer.

In this article, I’ll walk through steps required to create a new Angular application and connect it to a Firebase Cloudstore database. You’ll need to sign up with Firebase to create the database, but it’s free and painless. Let’s get to it!

We’ll begin by creating a new application using the Angular CLI:

$ ng new firebase-app
$ cd firebase-app

Next, we’ll create our Firebase Cloudstore database. Go to firebase.com, and create an account or sign in. Add a new project and Cloudstore database in test mode. Finally, add a web app to the project. When you do this, you’ll be presented with instructions for adding the Firebase SDK to your app, as shown below.


Copy/paste the configuration details into our applicaton’s app.module.ts file. (Don’t worry about putting it in the right spot–we’ll do that in a minute. Just get it in there so you have it.) Now it’s time to install the Firebase and the Angular Firebase module:

$ npm -i firebase @angular/fire

When you install @angular/fire a browser will open for you to authenticate with Firebase, and you’ll be given an authorization code. Copy/paste the code back to the command prompt to finish installing the module.

With the module installed, we complete the necessary changes in app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AngularFireModule } from '@angular/fire';
import { AngularFirestoreModule } from '@angular/fire/firestore'

import { AppComponent } from './app.component';
import { DemoComponent } from './demo/demo.component';

// Your web app's Firebase configuration
var firebaseConfig = {
  apiKey: "AIzaSyAx2cAfq9Pj3EzavXLkNc6_F9zWCyIayY4",
  authDomain: "ap-sample-1218e.firebaseapp.com",
  databaseURL: "https://ap-sample-1218e.firebaseio.com",
  projectId: "ap-sample-1218e",
  storageBucket: "ap-sample-1218e.appspot.com",
  messagingSenderId: "200601572991",
  appId: "1:200601572991:web:a335d1e106542870a9914a"
};
  
@NgModule({
  declarations: [
    AppComponent,
    DemoComponent
  ],
  imports: [
    BrowserModule,
    // Initialize Firebase
    AngularFireModule.initializeApp(firebaseConfig),
    AngularFirestoreModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

At this point, we’re actually done with setup and fully functional, but let’s add a new component to demonstrate its usage:

$ ng g c demo

Open demo.component.ts, and add the following:

import { Component, OnInit } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-demo',
  templateUrl: './demo.component.html',
  styleUrls: ['./demo.component.css']
})
export class DemoComponent implements OnInit {

  items: Observable<any[]>;
  
  constructor(
    private firestore: AngularFirestore
  ) { 
    this.items = this.firestore.collection('items').valueChanges();
  }

  ngOnInit(): void { }

  saveItem(value: string): Promise<any> {
    return this.firestore.collection('items').add({
      value: value
    });
  }
}

Add some simple markup to add & display items in demo.component.html:

<input #input type="text">
<button (click)="saveItem(input.value)">Add</button>

<ul>
    <li *ngFor="let item of items | async">
        {{item.value}}
    </li>
</ul>

Finally, remove boilerplate code from app.component.html and replace it with the demo component:

<app-demo></app-demo>

Now run the app. When values are submitted, they’re saved to our Firebase Cloudstore database, and the page will update in realtime as new records are added. Not bad for a few minutes of work!

$ ng serve --open

Make a Trello Clone in 15 Minutes

In the past weeks, I’ve written about how to make a drag & drop list using Angular CDK and how to enable dragging & dropping between multiple lists. Trello is the app that first comes to mind when I think of how this can be used to create a great experience.

At its core, a Trello board has just three pieces of functionality: create lists, create cards within the lists, and reorganize cards within and between the lists. The articles above give us all the tools we need to do this quickly ourselves, so let’s do it!

We’ll use the Angular CLI to do the following:

  1. Create a new app
  2. Add a list component
  3. Add a board component
  4. Make it pretty

Create New App

This first step’s pretty easy. We’re just going to use ng to create a new app and install the Angular CDK.

$ ng new lists-app --defaults
$ cd lists-app
$ npm install @angular/cdk

Do a quick check to make sure we’re starting in a good state:

$ ng serve --open

Add List Component

Now we’ll make our list component using ng generate component.

$ ng g c list

Hop into the code and make three changes. First, we need to import the DragDrop and Forms modules in src/app/app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { FormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { ListComponent } from './list/list.component';

@NgModule({
  declarations: [
    AppComponent,
    ListComponent
  ],
  imports: [
    BrowserModule,
    DragDropModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Next, update src/app/list/list.component.ts to support drag/drop and dynamic creation of items. We’ll add the items array to store our list items, a drop function to handle our drop/drop event, and an onSubmit function to add new items to our list.

import { Component, OnInit } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { NgForm } from '@angular/forms';

@Component({
  selector: 'app-list',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {

  items: string[] = [];

  constructor() { }

  ngOnInit(): void {
  }

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data,
        event.previousIndex,
        event.currentIndex);
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex);
    }
  }

  onSubmit(newItemForm: NgForm) {
    this.items.push(newItemForm.value.newItem);
    newItemForm.reset();
  }
}

The third step is to change our markup in src/app/list/list.component.html. This is just two parts, displaying the drag-&-droppable list items and accepting input for new items.

<div cdkDropList [cdkDropListData]="items" (cdkDropListDropped)="drop($event)">
    <div *ngFor="let item of items" cdkDrag>{{item}}</div>
</div>
<form #newItemForm="ngForm" (ngSubmit)="onSubmit(newItemForm)">
    <input name="newItem" ngModel type="text" placeholder="Enter a new item"><button type="submit">Add Item</button>
</form>

That’s it for our list component, but let’s make one more change to app/app.component.html so we can test. Replace its entire contents with the following:

<app-list></app-list>

Now let’s do another check-in. We should be able to add items to our list and move them around via drag & drop.

$ ng serve --open

Add Board Component

Once again, we look to ng generate component to create our board component.

$ ng g c board

Just like the list component allows for dynamic creation of list items, we want our board component to allow dynamic creation of lists. So, let’s modify src/app/board/board.component.ts to support this:

import { Component, OnInit } from '@angular/core';
import { ListComponent } from '../list/list.component';

@Component({
  selector: 'app-board',
  templateUrl: './board.component.html',
  styleUrls: ['./board.component.css']
})
export class BoardComponent implements OnInit {

  lists: ListComponent[] = [];

  constructor() { }

  ngOnInit(): void {
  }

  addList() {
    var newList = new ListComponent();
    this.lists.push(newList);
  }
}

And make the markup changes in src/app/board/board.component.html. One thing to note is the use of cdkDropListGroup. This makes all the lists connected and allows dragging & dropping between them.

<button (click)="addList()">Add List</button>
<div class="list-container" cdkDropListGroup>
    <app-list *ngFor="let list of lists"></app-list>
</div>

We’ll also modify src/app/board/board.component.css so that lists will be added horizontally.

.list-container {
    display: flex;
    flex-direction: row;
}

Finally, we’ll update app.component.html to use our board component instead of a single list:

<app-board></app-board>

Our board component is complete, so let’s do another check-in.

$ ng serve --open

Make It Pretty

At this point, the hard part’s done. We have our core functionality implemented. We can add lists, add items to the lists, and move the items around. Now let’s make it look nice!

Begin by installing Angular Material:

$ ng add @angular/material

Then import the MatCard, MatButton, and MatInput modules in src/app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatInputModule } from '@angular/material/input';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { ListComponent } from './list/list.component';
import { BoardComponent } from './board/board.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  declarations: [
    AppComponent,
    ListComponent,
    BoardComponent
  ],
  imports: [
    BrowserModule,
    DragDropModule,
    FormsModule,
    BrowserAnimationsModule,
    MatButtonModule,
    MatCardModule,
    MatInputModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Now we can use mat-card, mat-raised-button, and mat-form-field in src/app/list/list.component.html:

<div class="container">
    <div class="list" cdkDropList [cdkDropListData]="items" (cdkDropListDropped)="drop($event)">
        <mat-card class="list-item" *ngFor="let item of items" cdkDrag>
            <mat-card-content>
                <p>{{item}}</p>
            </mat-card-content>
        </mat-card>
    </div>
    <form #newItemForm="ngForm" (ngSubmit)="onSubmit(newItemForm)">
        <mat-form-field>
            <input matInput name="newItem" ngModel type="text" placeholder="Enter a new item">
        </mat-form-field>
        <button mat-raised-button type="submit" color="accent">Add Item</button>
    </form>
</div>

And we’ll add a little CSS to src/app/list/list.component.css:

.container {
    margin-right: 10px;
}

.container button {
    margin-left: 5px;
}

.list {
    padding: 10px;
    max-width: 100%;
    border: solid 1px #ccc;
    min-height: 60px;
    display: block;
    background: #fafafa;
    border-radius: 4px;
    overflow: hidden;
}

.cdk-drag-preview {
    box-sizing: border-box;
    border-radius: 4px;
    box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
    opacity: 0;
}

.cdk-drag-animating {
    transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.list-item {
    margin: 5px;
}

.list-item:last-child {
    border: none;
}

Then we’ll do some similar things to src/app/board/board.component.html and board.component.css:

<button (click)="addList()" mat-raised-button color="primary">Add List</button>
<div class="list-container" cdkDropListGroup>
    <app-list *ngFor="let list of lists"></app-list>
</div>
button {
    margin: 10px;
}

.list-container {
    display: flex;
    flex-direction: row;
    margin: 10px;
}

Now we’ve got fancy buttons, some input animations, and shadows while dragging, and it looks quite nice!

That’s where we’ll leave it today. The code I wrote while constructing this post can be found here.

Upgrade to Ubuntu 20.04

Image by rockiger from Pixabay

The newest LTS version of Ubuntu is scheduled to release on April 23. Once it’s released, upgrading is as easy as running a few commands. In this article, we’ll walk through the process of updating the current system and then performing the upgrade.

Before you upgrade, make sure your system is as up to date as possible. Do this by running the follwing commands:

sudo apt update 
sudo apt upgrade
sudo apt dist-upgrade
sudo apt autoremove

Now that you’ve confirmed you’re up to date, it’s time to do the upgrade.

sudo apt install update-manager-core
sudo do-release-upgrade

If you receive a No new release found message, it means the upgrade hasn’t been made available to you. It’s been noted that the upgrade path from 19.10 will not be enabled until a few days after release, and the upgrade from 18.04 LTS will not be enabled until a few days after the 20.04.1 release expected in late July. However, you can force the upgrade at your own risk by using the -d flag.

sudo do-release-upgrade -d

Angular Drag & Drop Between Lists

Last week I shared steps to make a drag & drop application in Angular in just a few minutes. This is great for sorting items within a single list, but drag & drop is more exciting when used for interaction between elements. Today we’ll look at what it takes to extend last week’s project to include two lists that we can drag & drop between.

Code from the previous article is available here.

The first thing we’ll do is modify the todo-list component to have two lists. Modify todo-list-component.html to have a second cdkDropList element as shown in the code below. We also need to indicate that these lists are connected.

<h2>TODO</h2>
<div 
    cdkDropList 
    #todoList="cdkDropList"
    [cdkDropListData]="tasks"
    [cdkDropListConnectedTo]="[doneList]"
    class="example-list" 
    (cdkDropListDropped)="drop($event)">
    <div class="example-box" *ngFor="let task of tasks" cdkDrag>{{task}}</div>
</div>
<h2>COMPLETED</h2>
<div
    cdkDropList 
    #doneList="cdkDropList"
    [cdkDropListData]="completedTasks"
    [cdkDropListConnectedTo]="[todoList]"
    class="example-list" 
    (cdkDropListDropped)="drop($event)">
    <div class="example-box" *ngFor="let task of completedTasks" cdkDrag>{{task}}</div>
</div>

Now we need to make a pair of changes to todo-list.component.ts. First, we need to add the completedTasks collection referenced by our second list. We also need to modify the drop event handler to determine whether we’re rearranging a list or moving an item between lists.

import { Component, OnInit } from '@angular/core';
import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-todo-list',
  templateUrl: './todo-list.component.html',
  styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
  tasks = [
    'Cleaning',
    'Gardening',
    'Shopping'
  ];
  completedTasks = [];

  constructor() { }

  ngOnInit(): void {
  }

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data, 
        event.previousIndex, 
        event.currentIndex);
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex);
    }
  }
}

That’s it–we’re done! Let’s run the application and see what happens.

$ ng serve --open

We can now rearrange items in the individual lists, as we could before, and move items between the two lists. Final code for this article is available on GitHub, here.

Feature image by Alexandra_Koch from Pixabay

E2E Angular Drag & Drop App in 5 Steps

Image by sachin_21 from Pixabay

Drag & drop is one of the best enhancements you can implement to give your app a modern feel and provide a great user experience, and Angular’s Component Dev Kit (CDK) makes it really easy to do. In this article, we’ll build a new Angular app from scratch using the Angular CLI and add a drag & drop todo list component.

Prereqs: Make sure you have the Angular CLI installed. You can test by running the command ng --version. If you need to install it, run the command npm install -g @angular/cli. (If you have problems installing on Linux, maybe this article can help!)

Step 1: Create a new app

Create a new app using the Angular CLI. We’ll also install the Angular CDK, and make sure it all works:

$ ng new my-app --defaults
$ cd my-app
$ npm install @angular/cdk
$ ng serve --open

Step 2: Import DragDropModule

In order to make use of Angular CDK’s drag & drop module, we need to import it. Open app.module.ts and import DragDropModule by making the following two edits:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { DragDropModule } from '@angular/cdk/drag-drop';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    DragDropModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 3: Add a new component

Now we’ll use the Angular CLI again to add a new component:

$ ng generate component todo-list

Next we need to modify the component to handle the drop event. We’ll also add some hard-coded test data to work with. Open todo-list.component.ts and make the following changes:

import { Component, OnInit } from '@angular/core';
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-todo-list',
  templateUrl: './todo-list.component.html',
  styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
  tasks = [
    'Cleaning',
    'Gardening',
    'Shopping'
  ];

  constructor() { }

  ngOnInit(): void {
  }

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.tasks, event.previousIndex, event.currentIndex);
  }
}

We also need to modify the component HTML. Update the contents of todo-list.component.html to be the following:

<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
    <div class="example-box" *ngFor="let task of tasks" cdkDrag>{{task}}</div>
</div>

Step 4: Use the new component

At this point, we’re actually done adding the basic drag & drop functionality, so let’s use our new component. Replace the entire contents of app.component.html with this:

<app-todo-list></app-todo-list>

Now, head back out to your terminal, re-launch the app, and–voila!–you’ve got drag & drop!

$ ng serve --open

(Optional) Step 5: Make it pretty

Sure it’s functional, but it doesn’t look very good! Spruce it up by adding the following CSS to todo-list.component.css:

.example-list {
  width: 500px;
  max-width: 100%;
  border: solid 1px #ccc;
  min-height: 60px;
  display: block;
  background: white;
  border-radius: 4px;
  overflow: hidden;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
              0 8px 10px 1px rgba(0, 0, 0, 0.14),
              0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

Ah, that feels a little better!

Intro to Unit Testing In Angular With Karma & Jasmine

Ah, unit testing. One of my favorite subjects. I’ve been big on unit tests for what seems like more than a decade now, but I’ve never dipped my toes into the UI unit test pool! It’s been long enough, though. It’s time to learn!

So, I’m taking my first look at writing tests for an Angular application using Karma and Jasmine. I thought this tutorial at scotch.io was a great first read. It provides overviews of what Karma and Jasmine are, and walks you through the steps of creating an Anuglar application and adding tests from scratch using the Angular CLI, and that’s basically the foundation I’m working with today.

In this article, I’m going to write some tests as I extend behavior on “an existing product” and also write tests for some untested code. I’ll be using my favorite boilerplate code–the .NET Core CLI Angular template–as my existing product, as it provides a functional appliction with some existing tests for us to build on.

If you haven’t already, start by creating a new Angular application by running the following command:

dotnet new angular -o my-app

Verify that can run your new application by running dotnet run in the my-app directory. my-app/ClientApp contains the Angular application. Since we’re going to be adding to existing test suite, we should verify that the existing tests run and pass. Run the following from my-app/ClientApp:

npm install
ng test

You should see output like the following, indicating that all tests have passed.

Now that we’ve verified that existing tests work, we can start making changes. Let’s assume we want to modify the Counter page to have a decrement button in addition to its increment button, because sometimes we click too many times. Before we start making changes to the Counter component itself, we can describe the desired behavior in the existing counter.component.spec.ts file, which already contains similar logic for the increment functionality. Most of what needs to be written can be copied and adjusted from the Increment test.

Here’s the test I wrote:

it ('should decrease the current count by 1 when Decrement is clicked', async(() => {
  const countElement = fixture.nativeElement.querySelector('strong');
  expect(countElement.textContent).toEqual('0');

  const decrementButton = fixture.nativeElement.querySelector('#decrement-button');
  decrementButton.click();
  fixture.detectChanges();
  expect(countElement.textContent).toEqual('-1');
}));

I can run tests again, and guess what–it’ll fail. Cool. So let’s make it work, which is just two steps. First we need to add the button to counter.component.html:

<h1>Counter</h1>

<p>This is a simple example of an Angular component.</p>

<p aria-live="polite">Current count: <strong>{{ currentCount }}</strong></p>

<button class="btn btn-primary" (click)="incrementCounter()">Increment</button>

<button class="btn btn-secondary" (click)="decrementCounter()" id="decrement-button">Decrement</button>

And then we need to add the logic for when it’s clicked in counter.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-counter-component',
  templateUrl: './counter.component.html'
})
export class CounterComponent {
  public currentCount = 0;

  public incrementCounter() {
    this.currentCount++;
  }

  public decrementCounter() {
    this.currentCount--;
  }
}

That’s it. Now we can run ng test again and see that we now have 3 passing tests.

Good stuff. Now, let’s turn our attention to some untested code. FetchDataComponent is functional but has no tests for its display of data retrieved from the API. We’ll need to mock the API call to return some data and then assert that results are displayed as expected.

Here’s my fetch-data.component.spec.ts file with a test that mocks the API call and asserts that data is populated in the table on the UI:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

import { FetchDataComponent } from './fetch-data.component';

describe('FetchDataComponent', () => {
    let component: FetchDataComponent;
    let fixture: ComponentFixture<FetchDataComponent>;
    let httpMock: HttpTestingController;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            declarations: [FetchDataComponent],
            providers: [{ provide: 'BASE_URL', useValue: 'https://my.fake.url/' }]
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(FetchDataComponent);
        component = fixture.componentInstance;
        httpMock = TestBed.get(HttpTestingController);
    });

    it('should retrieve weather forecasts', async(() => {
        const dummyForecasts = [
            {
                date: "date1",
                temperatureC: 0,
                temperatureF: 32,
                summary: "summary1"
            },
            {
                date: "date2",
                temperatureC: 100,
                temperatureF: 212,
                summary: "summary2"
            }
        ];

        const req = httpMock.expectOne('https://my.fake.url/weatherforecast');
        req.flush(dummyForecasts);

        expect(component.forecasts).toBe(dummyForecasts);
        fixture.detectChanges();
        const rowCount = fixture.nativeElement.querySelectorAll('table tr').length;
        expect(rowCount).toBe(3); // header plus two data rows
    }));
});

There’s a lot to unpack with this test, and I’m going to attempt to give a visual, summary-level explanation in the screenshot below.

Once again, we run tests using ng test and see that we now have passing tests for FetchDataComponent!

Installing Angular CLI on Ubuntu 18.04.4 LTS

I was all geared up to explore unit testing in Angular this morning–something I’ve been putting off learning for quite some time–and quickly hit an problem. I started by installing Angular CLI using apt install ng-common, but trying to use the Angular CLI’s ng command was launching Emacs!

It took me a minute to figure this out, and here’s what it took. First, I uninstalled:

sudo apt remove ng-common

Next, I installed using npm:

sudo npm install -g @angular/cli

This was common how-to-fix-it advice, but this didn’t resolve the problem for me. At this point ng was no longer launching an editor, but instead it was an unrecognized command. Luckily, I found the solution here.

I noticed in the install output that the bin location was listed. I was able to verify where the ng binary was located and add an alias. After that, ng --version worked successfully.

Here’s the alias I added to ~/.bashrc:

alias ng='/usr/local/lib/node_modules/@angular/cli/bin/ng'

Permission Bits in Base64 String

We’ve been kicking around some ideas of how to revamp user permissions in our application, and I was reflecting on how I’ve done it in the past. Previously, I worked on a client/server application where each user had a permission string that tracked all the rights they’d been granted, and global enumerations were used to identify which permissions and rights. I was explaining the approach to colleagues, and there was enough interest that I decided to recreate it.

Here’s the summary:

  • Each user has a Base64-encoded permission string
  • There’s an enumeration for the different modules; this identifies which byte in the permission string contains the rights
  • There’s an enumeration for the different rights which can be granted for a given module; these are used in bitwise operations to check or modify a user’s rights to a module

Enumerations

public enum Modules
{
    ModuleOne = 0,
    ModuleTwo = 1,
    ModuleThree = 2
}

public enum Permissions
{ 
    Create = 1,     // 0000 0001
    Read = 2,       // 0000 0010
    Update = 4,     // 0000 0100
    Delete = 8,     // 0000 1000
    Execute = 16    // 0001 0000
}

Granting Permission

Granting a permission is a simple as specifying the module and permission to grant. The module is used to identify the correct byte, and then a bitwise OR is performed to enable the corresponding permission bit.

string GrantPermission(string permissionString, Modules module, Permissions permission)
{
    if (permissionString == null)
    {
        permissionString = string.Empty;
    }

    var bytes = Convert.FromBase64String(permissionString);
    
    if (bytes.Length <= (int)module)
    {
        Array.Resize(ref bytes, (int)module + 1);
    }

    bytes[(int)module] |= (byte)permission;

    return Convert.ToBase64String(bytes);
}

Checking Permission

Checking a permission is very similar to granting except that we AND the permission mask. If the result isn’t zero, that means the user has the specified permission.

bool HasPermission(string permissionString, Modules module, Permissions permission)
{
    if (string.IsNullOrWhiteSpace(permissionString))
    {
        return false;
    }

    var bytes = Convert.FromBase64String(permissionString);

    if (permissionString.Length < (int)module)
    {
        return false;
    }

    var b = bytes[(int)module];

    return (b & (byte)permission) != 0;
}

Revoking Permission

Revoking a permission is similar as well. This time, we’ll invert the bits on the permission mask and AND it to the module permission byte so that all non-zero bits except the specified permission will remain the same.

string RevokePermission(string permissionString, Modules module, Permissions permission)
{
    if (permissionString == null)
    {
        permissionString = string.Empty;
    }

    var bytes = Convert.FromBase64String(permissionString);

    if (bytes.Length <= (int)module)
    {
        return permissionString;
    }

    bytes[(int)module] &= (byte)~permission;

    return Convert.ToBase64String(bytes);
}

Conclusion

This was just a quick & dirty re-implementation of something from the past. I haven’t put this code through the wringer, so there may be some bugs, and you could probably make it more developer-friendly by extracting it to its own class or moving this logic into some nice extension methods. It’s an efficient way to store and check permissions for a collection of modules. Depending on your needs, it might be a good fit. I’m also wondering if this could be used in a hybrid system–for example, perhaps it would make sense for different applications or scopes to have their own permission string within a larger system.