Skip to content

Introducing the Vue 3 and XState kit for starter.dev

Starter.dev is an open source community resource developed by This Dot Labs that provides code starter kits in a variety of web technologies, including React, Angular, Vue, etc. with the hope of enabling developers to bootstrap their projects quickly without having to spend time configuring tooling.

Intro

Today, we’re delighted to announce a new starter kit featuring Vue and XState! In this blog post, we’ll dive into what’s included with the kit, how to get started using it, and what makes this kit unique.

What’s included

All of our kits strive to provide you with popular and reliable frameworks and libraries, along with recommended tooling all configured for you, and designed to help you spin your projects up faster. This kit includes:

How to get started using the kit

To get started using this kit, we recommend the starter CLI tool. You can pass in the kit name directly, and the tool will guide you through naming your project, installing your dependencies, and running the app locally. Each kit comes with some sample components, so you can see how the provided tooling works together right away.

// npm
npm create @this-dot/create-starter -- --kit vue3-xstate-css
// yarn
yarn create @this-dot/create-starter --kit vue3-xstate-css

Now let’s dive into some of the unique aspects of this kit.

Vue 3

Vue is a very powerful JS framework. We chose to use Vue directly to highlight some of the features that make it such a joy to work with.

One of our favorite features is Vue’s single file components (SFC). We can include our JavaScript, HTML, and CSS for each component all in the same file. This makes it easier to keep all related code directly next to each other, making it easier to debug issues, and allowing less file flipping.

Since we’re in Vue 3, we’re also able to make use of the new Composition API, which looks and feels a bit more like vanilla JavaScript. You can import files, create functions, and do most anything you could in regular JavaScript within your component’s script tag. Any variable name you create is automatically available within your HTML template.

Provide and Inject

Another feature we got to use specifically in this starter kit is the new provide and inject functionality. You can read more details about this in the Vue docs, but this feature gives us a way to avoid prop drilling and provide values directly where they’re needed.

In this starter kit, we include a “greeting” example, which makes an API call using a provided message, and shows the user a generated greeting. Initially, we provided this message as a prop through the router to the greeting component. This works, but it did require us to do a little more legwork to provide a fallback value, as well as needing our router to be aware of the prop.

Using the provide / inject setup, we’re able to provide our message through the root level of the app, making it globally available to any child component. Then, when we need to use it in our GreetView component, we inject the “key” we expect (our message), and it provides a built-in way for us to provide a default value to use as a fallback. Now our router doesn’t need to do any prop handling! And our component consistently works with the provided value or offers our default if something goes wrong.

// src/main.ts

const app = createApp(App);
app.provide('query', 'from This Dot Labs!');
// src/views/GreetView.vue

<script setup lang="ts">
import { inject } from 'vue';
const providedQuery = inject('query', '');
const { state } = useMachine(greetMachine(providedQuery));
</script>

Using XState

If you haven’t had a chance to look into XState before, we highly recommend checking out their documentation. They have a great intro to state machines and state charts that explains the concepts really well.

One of the biggest mindset shifts that happens when you work with state machines is that they really help you think through how your application should work. State machines make you think explicitly through the different modes or “states” your application can get into, and what actions or side effects should happen in those states. By thinking directly through these, it helps you avoid sneaky edge cases and mutations you don’t expect.

Difference between Context and State

One of the parts that can be a little confusing at first is the difference between “state” and “context” when it comes to state machines. It can be easy to think of state as any values you store in your application that you want to persist between components or paths, and that can be accurate. However, with XState, a “state” is really more the idea of what configurations your app can be in.

A common example is a music player. Your player can be in an “off” state, a “playing” state, or a “paused” state. These are all different modes, if you will, that can happen when your music player is interacted with. They can be values in a way, but they’re really more like the versions of your interface that you want to exist. You can transition between states, but when you go back to a specific state, you expect everything to behave the same way each time. States can trigger changes to your data or make API calls, but each time you enter or leave a state, you should be able to see the same actions occur. They give you stability and help prevent hidden edge cases.

Values that we normally think of as state, things like strings or numbers or objects, that might change as your application is interacted with. These are the values that are stored in the “context” within XState. Our context values are the pieces of our application that are quantitative and that we expect will change as our application is working.

export const counterMachine = createMachine(
  {
	id: 'Counter',
	initial: 'active',
	context: {
		count: 0,
	},
	states: {
		active: {
			on: {
				INC: { actions: 'increment' },
				DEC: { actions: 'decrement' },
				RESET: { actions: 'reset' },
			},
		},
	},
  },
  {
	actions: {
		increment: assign({ count: (context) => context.count + 1 }),
		decrement: assign({ count: (context) => context.count - 1 }),
		reset: assign({ count: (context) => (context.count = 0) }),
	},
  }
);

Declaring Actions and Services

When we create a state machine with XState, it accepts two values- a config object and an options object. The config tells us what the machine does. This is where we define our states and transitions. In the options object, we can provide more information on how the machine does things, including logic for guards, actions, and effects.

You can write your actions and effect logic within the state that initiates those calls, which can be great for getting the machine working in the beginning. However, it’s recommended to make those into named functions within the options object, making it easier to debug issues and improving the readability for how our machine works.

Cypress Testing

The last interesting thing we’d like to talk about is our setup for using component testing in Cypress! To use their component testing feature, they provide you with a mount command, which handles mounting your individual components onto their test runner so you can unit test them in isolation.

While this works great out of the box, there’s also a way to customize the mount command if you need to! This is where you’d want to add any configuration your application needs to work properly in a testing setup. Things like routing and state management setups would get added to this function.

Since we made use of Vue’s provide and inject functions, we needed to add the provided value to our mount command in order for our greeting test to properly work. With that set up, we can allow it to provide our default empty string for tests that don’t need to worry about injecting a value (or when we specifically want to test our default value), and then we can inject the value we want in the tests that do need a specific value!

// cypress/support/component.ts

Cypress.Commands.add('mount', (component, options = {}) => {
  options.global = options.global || {};
  options.global.provide = options.global.provide || {};
  return mount(component, options);
});

Conclusion

We hope you enjoy using this starter kit! We’ve touched a bit on the benefits of using Vue 3, how XState keeps our application working as we expect, and how we can test our components with Cypress.

Have a request or a question about a [starter.dev] project? Reach out in the issues to make your requests or ask us your questions. The project is 100% open sourced so feel free to hop in and code with us!

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

Getting Started with Vuetify in Vue 3 cover image

Getting Started with Vuetify in Vue 3

If you're here to learn about Vuetify and how to use it with Vue 3, you've come to the right place. In this article, we'll introduce Vuetify, a Material Design component framework for Vue.js, and walk you through setting it up in a Vue 3 project. We will also build a simple web app that allows us to create and vue jokes. Oh, sorry, view jokes! What is Vuetify? Vuetify is a VueJS Material Design component framework. That is to say that Vuetify provides us with prebuilt components and features (e.g. internationalization, tree shaking, theming, etc.) to help us build Vue applications faster. Additionally, Vuetify components have built-in standard functionality, such as validation in form inputs, or various disabled states in buttons. You can check out a full list of components supported by Vuetify on their official documentation website. Setting things up Before we get started, you will need the following on your machine: - NodeJS installed - A NodeJS package manager e.g. yarn or npm installed - Intermediate knowledge of Vue 3.x with the composition API If you’d like to follow along, feel free to clone the github repository for this article. `shell git clone https://github.com/thisdot/blog-demos.git or `git@github.com:thisdot/blog-demos.git` for ssh then cd into the project directory cd getting-started-with-vuetify ` Installing Vuetify We will be using yarn` to install and setup Vuetify 3 (the version that supports Vue 3) in this case. First, let's create a new Vuetify project. You can do this by running the following command in your terminal or command prompt: `shell yarn create vuetify ` This will create a new Vuetify project. For our jokes app, we will use the Essentials (Vuetify, VueRouter, Pinia)` preset when creating our app. We will also be using TypeScript for our app, but this is not necessary. Since VueJS allows us to build incrementally, if you would like to instead add Vuetify to an existing project, you can use the manual steps provided by the Vuetify team. Testing our application Once we have installed and configured our application, cd` into the project's directly, and run the app using the following command: `shell yarn dev #or npm run dev` (if using npm instead) ` Visit localhost:3000/` to see your app in action. Vuetify project folder structure Our Vuetify project is generally structured as follows: - public` - Contains static assets that do not need preprocessing eg. our application `favicon` - src` - Contains our VueJS source code. We'll mostly be working here. - assets` - Assets that need to be preprocessed eg. our images that may need to be compressed when building for production - components` - layouts` - Layouts - plugins` - Everything gets wired up here (registration of our app as well as vuetify, our router & pinia) - router` - Vue router-related functionality - store` - Pinia store - styles` - views` - our web app's "pages" Worth noting before building It is worth noting that all components in Vuetify start with a v-` for example: - v-form` is a form - v-btn` is a button component - v-text-field` is an input field and so on and so forth. When creating your own components, it is recommended to use a different naming approach so that it is easier to know which components are Vuetify components, and which ones are your components. Building our Vuetify application We will build a web app that allows us to add, view and delete jokes. Here are the steps we will take to build our app: - Delete unnecessary boilerplate from generated Vuetify app - Add a joke` pinia store - we'll be using this to store our jokes globally using Pinia - Create our joke components - components/jokes/CreateJokeForm.vue` - the form that allows us to add jokes - components/jokes/JokeList.vue` - Lists our jokes out. - Add our components to our Home.vue` to view them in our home page Setting up the jokes pinia store In the src/store/` directory, create a new file called `joke.ts` that will serve as our Pinia store for storing our jokes. The file content for this will be as follows: `ts import { defineStore } from "pinia"; export interface Joke { id: number; title: string; punchline: string; } export const useJokeStore = defineStore({ id: "joke", state: () => ({ jokes: [] as Joke[], }), actions: { addJoke(joke: Joke) { this.jokes.push(joke); }, removeJoke(id: number) { this.jokes = this.jokes.filter((joke) => joke.id !== id); }, }, }); ` This code defines a special storage space called a "store" for jokes in our Vue.js app. This store keeps track of all the jokes we've added through our app's form. Each joke has an ID, title, and punchline. The addJoke` function in the store is used to add a new joke to the store when a user submits the form. The `removeJoke` function is used to delete a joke from the store when a user clicks the delete button. By using this store, we can keep track of all the jokes that have been added through the app, and we can easily add or remove jokes without having to manage the list ourselves. Creating the joke components CreateJokeForm.vue Create a file in src/components/jokes/` called `CreateJokeForm.vue`. This file defines a Vue.js component that displays a form for adding new jokes. The file should have the following contents: Template section `html Submit Joke ` In the template section, we define a form using the v-form component from Vuetify. We bind the form's submit event to the submitJoke method, which will be called when the form is submitted. Inside the form, we have two text fields" one for the joke title, and one for the punchline. These text fields are implemented using the v-text-field component from Vuetify. The label prop sets the label for each text field, and the outlined prop creates an outlined style for each text field. The required prop sets the fields as required, meaning that they must be filled in before the form can be submitted. Finally, we have a submit button implemented using the v-btn component from Vuetify. The button is disabled until both the title and punchline fields are filled in, which is accomplished using the :disabled prop with a computed property that checks if both fields are empty. Script section `ts import { computed, ref } from "vue"; import { Joke, useJokeStore } from "@/store/joke"; const jokeStore = useJokeStore(); const jokeTitle = ref(""); const jokePunchline = ref(""); const joke = computed(() => ({ id: jokeStore.jokes.length + 1, title: jokeTitle.value, punchline: jokePunchline.value, })); function submitJoke() { jokeStore.addJoke(joke.value); jokeTitle.value = ""; jokePunchline.value = ""; } ` In the script section, we import some functions and types from Vue.js and the joke` store. We then define a `jokeStore` variable that holds the instance of the `useJokeStore` function from the `joke` store. We also define two ref`s, `jokeTitle`, and `jokePunchline`, which hold the values of the form's title and punchline fields, respectively. We then define a computed property, joke`, which creates a new `Joke` object using the `jokeTitle` and `jokePunchline` refs, as well as the length of the `jokes` array in the `jokeStore` to set the `id` property. Finally, we define a submitJoke` function that calls the `addJoke` method from the `jokeStore` to add the new `joke` object to the store. We also reset the `jokeTitle` and `jokePunchline` refs to empty strings. JokeList.vue Template section This one looks bulky. But in essence, all we are doing is displaying a list of jokes when they are found, and a message that lets us know that there are no jokes if we have none that have been added. `html My Jokes {{ joke.title }} {{ joke.punchline }} mdi-delete You have no jokes. Add some! ` In the template section, we define a v-card` component, which is a container component used to group related content in Vuetify. The card contains a title, which includes an excited emoticon icon from the `mdi-emoticon-excited-outline` icon set from Material Design Icons, displayed using the `v-icon` component. The jokes are displayed in a v-list`, which is a component used to display lists in Vuetify. Each joke is represented by a `v-list-item` containing a title and subtitle. The `v-row` and `v-col` components from Vuetify are used to divide each list item into two columns: one column for the joke title and punchline, and another column for the delete button. The delete button is implemented using the v-btn` component from Vuetify. The button is red, and outlined using the `color="error"` and `variant="outlined"` props, respectively. The `@click` event is used to call the `deleteJoke` function when the button is clicked. If there are no jokes in the jokeStore`, the component displays an `v-alert` component with a message to add some jokes. Script section `ts import { Joke, useJokeStore } from "@/store/joke"; const jokeStore = useJokeStore(); function deleteJoke(joke: Joke) { jokeStore.removeJoke(joke.id); } ` In the script section, we import some functions and types from the joke` store. We then define a `jokeStore` variable that holds the instance of the `useJokeStore` function from the `joke` store. We also define a deleteJoke` function that takes a `joke` object as an argument and calls the `removeJoke` method from the `jokeStore` to remove the joke from the store. This component is called JokeList.vue` and displays a list of jokes using Vuetify components like `v-card`, `v-list`, `v-list-item`, `v-row`, `v-col`, and `v-btn`. The component includes a `deleteJoke` function to remove a joke from the `jokeStore` as well. Wiring it up To display our form as well as list of jokes, we will go to the src/views/Home.vue` file and change its contents to the following: `html import CreateJokeForm from "@/components/jokes/CreateJokeForm.vue"; import JokeList from "@/components/jokes/JokeList.vue"; ` The Home.vue` file defines a Vue.js view that displays the home page of our app. The view contains a `v-container` component, which is a layout component used to provide a responsive grid system for our app. Inside the v-container`, we have a `v-row` component, which is used to create a horizontal row of content. The `v-row` contains two `v-col` components, each representing a column of content. The `cols` prop specifies that each column should take up 12 columns on small screens (i.e. the entire row width), while on medium screens, each column should take up 6 columns (i.e. half the row width). The first v-col` contains the `CreateJokeForm` component, which displays a form for adding new jokes. The second `v-col` contains the `JokeList` component, which is used to display a list of jokes that have been added through the form. In the script` section of the file, we import the `CreateJokeForm` and `JokeList` components, and register them as components for use in the template. This view provides a simple and responsive layout for our app's home page, with the CreateJokeForm` and `JokeList` components displayed side by side on medium screens and stacked on small screens. Bonus: Layouts & Theming Layouts Even though we had no need to adjust our layouts in our current jokes application, layouts are an important concept in Vuetify. They allow us to pre-define reusable layouts for our applications. These could include having a different layout for when users are logged in, and when they are logged out or layouts for different types of users. In our application, we used the default Layout (src/layouts/default/Default.vue`) but Vuetify offers us the flexibility to build different layouts for the different domains in our applications. Vuetify also supports nested layouts. You can learn more about layouts in Vuetify in the official Vuetify documentation. Theming If you have specific brand needs for your application. Vuetify has a built-in theming system that allows you to customize the look and feel of your application. You can learn more about theming in the official Vuetify theming documentation. Conclusion In this article, we introduced Vuetify, and covered how to set it up with Vue 3. We built a VueJS app that allows us to add and manage jokes. We also discussed how to use various Vuetify components to compose our UI, from v-form` for declaring forms to `v-row` for creating a row/column layout, and `v-list` for displaying a list of items among others. With this knowledge, you can start using Vuetify in your Vue 3 projects and create stunning user interfaces. Also, if you'd like to start your own VueJS project but need help with how to structure it or would like to skip the tedious setup steps of setting up a VueJS project, you can use the Vue 3 Starter.dev kit to skip the boilerplate and start building! Next steps for learning Vuetify and Vue 3 development Now that you have an understanding of Vuetify, it's time to dive deeper into its features, and explore more advanced use cases. To continue your learning journey, consider the following resources: 1. Official Vuetify documentation: The Vuetify documentation is an excellent resource for learning about all the features and components Vuetify offers. 2. Vue 3 documentation: To get the most out of Vuetify, it's essential to have a solid understanding of Vue 3. Read the official Vue 3 documentation and practice building Vue applications. Happy coding, and have fun exploring the world of Vuetify and Vue 3!...

Converting Your Vue 2 Mixins into Composables Using the Composition API cover image

Converting Your Vue 2 Mixins into Composables Using the Composition API

Introduction There are two main ways to add additional functionality to our Vue components: mixins and composables. Adding mixins is similar to adding properties through the options API because you can add them by creating and "injecting" the properties into your Vue components. However, this comes with a couple of problems, the primary one being that mixins can lead to messy code and conflicting names. This is because they dump their properties into the component. With the composition API that was introduced in Vue 3, we can mitigate the problems we just mentioned by using composables. The Vue team created composables as a better alternative to mixins that allow for greater code reuse and structure. Additionally, they are designed specifically for each component, and don't cause any naming issues. We'll see more of this later on. In this article, we'll show you how to take your mixins and turn them into composables for even more functionality and ease of maintenance. The Structure of a Mixin Now, before we say goodbye to mixins and embrace composables, we need to understand the main differences between the two. In a way, a mixin is like a bag of magic tricks you can easily add to your components to do their magic. However, having all these tricks in her one backpack can make it challenging to keep track of what's happening, especially if multiple components use the same mixin. For example; if there was a data` property called `message` in our `mixin`, and a property by the same name (`message`) in our component, the mixin `message` would clash with our component's `message` (which would have been declared in the `data` section of our component). We will be going over composables next. But before we do, here's an example of a mixin: `javascript export const testMixin = { data() { return { message: "This message is in our mixin!" }; }, methods: { printMessage() { console.log('Message:',this.message); } } }; ` In this example, the mixin` contains a `data` function that returns an object with a `message` property, as well as a `printMessage` method that logs the given message to the console. To use this mixin in a component, the component would need to import the mixin, and add it to its `mixins` option: `javascript import testMixin from './testMixin.js'; export default { mixins: [testMixin], // The rest of your code }; ` Creating the Composable Composables are a new feature that was added within the Vue 3 composition API. Composables are also called composition functions. They allow us to add functionality to components in a cleaner, more maintainable way. Instead of merging properties and methods directly into the component like in a mixin, composables are explicitly tailored to the component, and return a value or a set of reactive properties. What this means is that with composables, you can group all the functionality that is related together (rather than in separate data`, `computed`, and `methods` sections). Again, you also don't have name collisions anymore because each call to a composable will give you an object that is not tied to your existing component. That means that you can resolve any naming conflicts at development time rather than breaking your application because of the same thing happening at runtime as is the case with mixins. Here is a simple composable that achieves the same result as the mixin we had created earlier. `javascript import { ref } from 'vue'; export const useMessage = () => { const message = ref("This message is in our composable!"); const printMessage = () => { console.log(message.value); }; return { message, printMessage }; }; ` Here's how the composable can be used in a .vue` file component: `js import { useMessage } from './useMessage'; const { message, printMessage } = useMessage(); // You can now access the message and printMessage in your template after extracting them from the useMessage composable ` With this setup, the component will access the reactive message` property and the `printMessage` method returned by the composable. Notice that composables allow us to add functionality to components more intuitively and easier to understand without the clutter of a mixin's structure. In general, the naming convention for composables is useX`, where `X` is the domain our composable is meant to represent. In this case, since our functionality is around our message, our composable is called `useMessage`. Conclusion In this article, we've explored how to convert Vue 2 mixins to Composition API composables. We started by understanding the structure of a simple Vue 2 mixin, which can make components challenging to maintain due to how they merge properties and methods directly into the component. Next, we looked at how to create a composable in Vue.js 3's Composition API, which allows us to add functionality to cleaner and more maintainable components. Composables are explicitly tailored to the component and return a value or a set of reactive properties, making them easier to understand and use in components. With this understanding of mixins and composables, developers can now confidently convert their Vue 2 mixins to Vue 3 composables, taking advantage of the new and improved functionality offered by the Composition API. By doing so, developers can create more manageable components to maintain, test, and debug, resulting in a better user experience for their applications. Also, if you are looking to start a new Vue JS project and are not sure of how to structure your project, feel free to check out our Vue JS starter.dev GitHub showcases which showcase (no kidding) a mini-GitHub clone application built using Vue in different ways (eg. using Nuxt, Quasar, Vite, etc). Alternatively, if you are simply looking to start a new project without worrying about all the config required to do so, you can check out the Vue JS starter kit instead. Either way, thanks for stopping by!...

How to Create a Custom Login Command with Cypress, React, & Auth0 cover image

How to Create a Custom Login Command with Cypress, React, & Auth0

Auth0 is a tool for handling user authentication and account management. Cypress is a tool for writing end to end tests for your application. Getting these two technologies to work together can have a few gotchas, especially if your application is set up to have the end to end tests in a separate folder from your frontend code. Today, we’ll go over the steps you’ll need to implement so you can have a custom Cypress command to log in a user, and also let your front end code continue to work safely! --- Setup For this setup, we're making the choice to keep our end to end tests in one folder, and our front end code in a sibling folder. You don't have to do it this way. It's just the choice we've made for this project. So our folder setup has two root level folders, "cypress" and "app": `shell cypress | support | cypress.config.ts app | tsconfig.ts | src ` --- React Adjustments Auth0 provides a default login form for you to use. We’ll focus on the fact that the forms use web workers to store the user’s access tokens in memory. This helps them keep that access token more secure from attackers. This is important to note for Cypress. But because Cypress does not have a way to interact with that web worker. So we need to be able to detect when our application is running the site through Cypress so we can adjust how we get the access token. We’ll need to update the way our Auth0 form saves that token based on if our app is running normally, or if it’s running through Cypress. To do this, we can add a check to the cacheLocation` prop in our Auth0Provider field that wraps around our root app component. `app/index.ts`** `ts ` > Important note if you’re using TypeScript:** > If your project is using TypeScript, there will be two more small changes you might need to make. > First, your editor may be yelling at you that it doesn’t know what Cypress` means, or that it doesn’t exist on the `window` interface. So you can make a type file to solve this. `src/types/globals.d.ts`** `ts Interface Window { Cypress: boolean } ` > Then, in the root level tsconfig` file, we need to add an option to the `compilerOptions` section so it knows to look for this file we just made and use it. Then your editor should be happy! :) `app/tsconfig.ts`** `ts “compilerOptions”: { “typeRoots”: [“src/types/globals”] }, ` And that’s all your front end code should need to work! It’ll continue to work as expected when running your app locally or in production. But now when you run your Cypress tests, it’ll store that access token in local storage instead, so we can make use of that within our tests. --- Cypress Adjustments Now we can make our custom login command! Having a custom command for something like logging in a user that might need to be repeated before each step is super helpful. It makes it faster for your tests to get started, and doesn’t need to rely on the exact state of your UI so any UI changes you might make won’t affect this command. There’s a few different things we’ll need to handle here: - Writing the custom command itself - Making an e2e` file where we’ll set up this command to run before each test - Updating the Cypress config file to know about the environment variables we’ll need - If you’re using TypeScript, you’ll also need to make a separate “namespace” file and an index file to pull all the pieces together. We’ll start with the meat of the project: making that custom command! I’ll share the whole command first. Then we’ll walk through the different sections individually to cover what’s going on with each part. The Custom Command Cypress automatically sets up a support` folder for you, so you’ll likely already find a `commands` file in there. This will go in that file. If you don’t have it, you can create it! `cypress/support/commands.ts`** `ts 1 /// 2 Cypress.Commands.add('loginWithAuth0', (username, password) => { 3 const clientid = Cypress.env('auth0_client_id'); 4 const clientsecret = Cypress.env('auth0_client_secret'); 5 const audience = Cypress.env('auth0audience'); 6 const scope = Cypress.env('auth0scope'); 7 8 cy.request({ 9 method: 'POST', 10 url: https://${Cypress.env('auth0_domain')}/oauth/token`, 11 body: { 12 granttype: 'password', 13 username, 14 password, 15 audience, 16 scope, 17 clientid, 18 clientsecret, 19 }, 20 }).then(({ body }) => { 21 const { accesstoken, expires_in, id_token, token_type } = body; 22 cy.window().then((win) => { 23 win.localStorage.setItem( 24 @@auth0spajs@@::${client_id}::${audience}::${scope}`, 25 JSON.stringify({ 26 body: { 27 clientid, 28 accesstoken, 29 idtoken, 30 scope, 31 expiresin, 32 tokentype, 33 decodedToken: { 34 user: JSON.parse( 35 Buffer.from(idtoken.split('.')[1], 'base64').toString('ascii') 36 ), 37 }, 38 audience, 39 }, 40 expiresAt: Math.floor(Date.now() / 1000) + expiresin, 41 }) 42 ); 43 }); 44 }); 45 }); ` The first piece to note is line 2. Cypress.Commands.add('loginWithAuth0', (username, password) => {...` This is what actually tells Cypress “hey, this is a command you can use”. The name can be anything you want. I’m using “loginWithAuth0”, but you could call it “login” or “kitty” or whatever makes the most sense for your project. Just make sure you know what it means! :) Lines 3-6 are setting our the environment variables that the Auth0 call will use. Then, on line 8, we use Cypress to make the actual request to Auth0 that allows us to log in. For this use case, we’re choosing to login with a username and password, so we tell the call that we’re using the “password” type and send all the necessary environment variables for the call to work. (You can find info on what these values should be from the Auth0 docs.) Then, on lines 20 and 21, we’re starting to deal with the response we get back. If the call was successful, this should contain the information on the test user we just signed in and the access token we need for them. So we extract those values from the body of the response so we can use them. On line 22, we again use Cypress to get the browser’s window, and let us store the user access token. We'll use localStorage` for this as seen on line 23. Important note here! Pay extra special attention to the way the string is set up that we’re storing in localStorage on line 24! Auth0 needs the access token to be stored in this specific manner. This is the only way it will find our token! So make sure yours is set up the same way.** The rest of the code here is taking the information we got from the Auth0 call and adding it to our new localStorage` value. You’ll see that on line 35 we’re parsing the user information so we have access to that, and then setting an expiration on line 40 so the token won’t last forever. And that’s it - our command is set up! Now on to the rest of the things we need to set up so we can actually use it. :) Supporting Files If you have commands that should be run before every test call, you can create an e2e` file in your `support` folder. These are your global imports and functions that will apply to all of your Cypress test files. Here we’ll import our custom commands file and actually call our new command in a beforeEach` hook, passing in the test username and password we want to use. `cypress/support/e2e.ts`** `ts import "./commands"; beforeEach(function () { cy.loginWithAuth0( Cypress.env("auth0username"), Cypress.env("auth0password") ); }); ` > TypeScript Note:** > To get the typings to work properly for your custom commands, you’ll need to do two things. > First, you’ll want to make a new file called namespace` in your “support” folder. Then, you’ll want to declare the Cypress namespace in there, and set up a line for your new custom command so Cypress knows the type for it. (If you originally edited the default Cypress `commands` file, you’ll also want to go to the bottom of that file and remove the namespace section from it - this is replacing that.) `ts declare namespace Cypress { interface Chainable { loginWithAuth0(username: string, password: string): Chainable; } } ` > Then in your support` folder, create an “index.ts” file and add your commands and namespace imports to it. `ts Import “./commands”; Import “./namespace”; ` > That should clear up any TypeScript related warnings in your files! The final piece is updating our Cypress configuration file. We need to add all the environment variables we need in here so Cypress is aware of them and we don’t have them hard coded in our files. `cypress.config.ts`** `ts import { defineConfig } from "cypress"; require("dotenv").config(); export default defineConfig({ e2e: { baseUrl: "http://localhost:3000", }, env: { auth0username: process.env.AUTH0_TEST_USERNAME, auth0password: process.env.AUTH0_TEST_PASSWORD, auth0domain: process.env.AUTH0_DOMAIN, auth0audience: process.env.AUTH0_AUDIENCE, auth0scope: process.env.AUTH0_SCOPE, auth0client_id: process.env.AUTH0_CLIENT_ID, auth0client_secret: process.env.AUTH0_CLIENT_SECRET, }, }); ` Wrap Up With that in place, you should be good to go! Whenever you run your Cypress tests now, you should see that a user is automatically logged in for you so you start on the first page of your app. We hope this helps! If you run into any issues or have questions on anything, please feel free to reach out to us. Leave a comment on this post or ask in our Discord. We’ll be happy to help!...

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels cover image

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels

In this episode of the engineering leadership series, Kathy Keating, co-founder of CTO Levels and CTO Advisor, shares her insights on the role of a CTO and the challenges they face. She begins by discussing her own journey as a technologist and her experience in technology leadership roles, including founding companies and having a recent exit. According to Kathy, the primary responsibility of a CTO is to deliver the technology that aligns with the company's business needs. However, she highlights a concerning statistic that 50% of CTOs have a tenure of less than two years, often due to a lack of understanding and mismatched expectations. She emphasizes the importance of building trust quickly in order to succeed in this role. One of the main challenges CTOs face is transitioning from being a technologist to a leader. Kathy stresses the significance of developing effective communication habits to bridge this gap. She suggests that CTOs create a playbook of best practices to enhance their communication skills and join communities of other CTOs to learn from their experiences. Matching the right CTO to the stage of a company is another crucial aspect discussed in the episode. Kathy explains that different stages of a company require different types of CTOs, and it is essential to find the right fit. To navigate these challenges, Kathy advises CTOs to build a support system of advisors and coaches who can provide guidance and help them overcome obstacles. Additionally, she encourages CTOs to be aware of their own preferences and strengths, as self-awareness can greatly contribute to their success. In conclusion, this podcast episode sheds light on the technical aspects of being a CTO and the challenges they face. Kathy Keating's insights provide valuable guidance for CTOs to build trust, develop effective communication habits, match their skills to the company's stage, and create a support system for their professional growth. By understanding these key technical aspects, CTOs can enhance their leadership skills and contribute to the success of their organizations....