Build your first GitHub action: Step-by-step guide - Presentation
Video transcript
We utilized ChatGPT to enhance the grammar and syntax of the transcript.
All right, welcome everybody. My name is Paul, and I am with Platform.sh, and we are proudly the host for SymfonyCon. So, I'm here today to talk about GitHub actions. Now, in my presentations, I do like to give a series of warnings. For those of you listening in, I've got an animation of a red light flashing to give you a heads up, and in this case, I'm going to start my presentation with some warnings.
The first is that I am in no way a GitHub actions expert. However, over the last 18 months, I have been responsible for building and maintaining a large collection of custom actions and workflows to help automate a lot of the responsibilities that my team has. In that time, I came across many areas of frustration and confusion on the platform, and so that's what kind of prompted this presentation. My goal, or my hope, is that I can help you navigate around those more confusing areas and jumpstart you on your way to automating inside of GitHub.
The next warning I'll give you is that I expect you to interact with me. I'm going to ask a lot of questions during the presentation, and I need you to respond to me. The last warning is that I'm going to talk really quickly because this is actually a three-hour workshop that I'm trying to condense down into 35 minutes.
So, with that said, what I want to cover in this quick 35 minutes is: I want to talk about the actions platform and what it is. I then want to move through the different components and pieces of GitHub Actions that you're going to use to build out your automation. I'm then going to talk about one of those pieces called the workflow, and we'll dig into the workflow and talk about the pieces and parts of the workflow that are required and that you'll be utilizing. Then, together, we're going to build our first workflow—so that's a hint that that's where I need you to interact with me. Next, we'll move into actions and building out custom actions, talking about the pieces and parts that are inside of a custom action and those things that are required for you to build a custom action.
I need to move to the right—sorry, keep moving to the right, I have to go all the way to the right. Okay, is that better? All right, sorry, I like to walk, so I didn't know I had to stay over here. Then along the way—oh, sorry—then we're going to build our first custom action together, see that in use. As we go through, I'll give you more warnings, some caveats, and gotchas—things to watch out for. Lastly, hopefully with enough time, I'll kind of show you how all these things come together to actually build out true, in-life automation.
So, the first thing is: what is GitHub Actions? Well, GitHub Actions is a service from GitHub. It is a continuous integration and continuous delivery platform for you to automate things surrounding your codebase, whether that's building or deploying or testing. It's what gives you the ability to automate, and it doesn't just have to be code-related. It could also be project management. So, anything that you need to do when something happens to your codebase, we can utilize this platform in order to automate things.
So, what are the pieces and parts that you're going to use? Well, you're going to work with things called workflows. We've got events, runners, jobs, steps, and GitHub actions (but with a lowercase 'a'). If you're listening in, I've got a gentleman saying, "Wait, what?" Yeah, so GitHub Actions with a capital 'A' refers to the platform—the whole entire automation platform. If you see GitHub actions with a lowercase 'a', that refers to a singular modular, self-contained piece of automation that you can use in your automation. Now, sometimes they are referred to as custom actions—look at that there—and sometimes just as actions. But just be aware that this happens. In fact, the entire platform—it's unfortunate the way that GitHub decided to name certain things—they often will use a word, and then depending on where the word is used, it will have a different meaning. If English is not your first language, that's exceptionally hard. One case is "status" versus "checks" versus "status checks," and those are three different things, not necessarily related.
So, if you find yourself in a situation where what you're seeing—do I need to scoot over again? I'm sorry—if what you're seeing doesn't match what you're reading in the docs, you may have come across one of these similar situations. All right, so we've got workflows, runners, jobs, steps, and actions. How do these pieces all fit together? Well, we configure our automation in a workflow file—that's where we define all the automation that we want to have happen. Those workflows, those automation pieces, are going to be triggered by some event—something happening to our codebase—that then kicks off a series of jobs. Jobs are where we define a bunch of steps, and those steps are what actually perform our business logic, whether they're calling other shell commands or shell scripts, or they're calling one of those actions that I mentioned earlier.
So, what I want to do now is dig into each of these components and kind of define them and explain them a little bit more. So, the workflow—I mentioned that the workflow itself is the automation. We configure it in what is called a workflow file. A workflow file has to be a YAML file—its name does not matter, it just has to be a YAML file—but it does have to be contained inside of a .github/workflows
directory at the root of your repository. Now, your repository itself can have multiple workflows. You can have a workflow triggered on the same—you can have a ton of workflows triggered on the same event; they can each be triggered on different events; one workflow can be triggered by multiple events; whatever your particular situation is—it just has to be YAML and it has to be in that directory.
An event then is some activity that occurs to your codebase, whether that's a push, a pull request, it could be somebody labeled something—whatever, it's just something that occurs. Now, you have a whole bunch—I'm gonna flip over real quick—you have a whole bunch of events, and I've got those here. You can see here on the right-hand side a ton of events to give you fine-grain control of exactly when you want those workflows to be triggered. Now, I will mention that in the docs, they occasionally switch from using the word "event" to "workflow triggers." So, if you see a workflow trigger, it's just the same thing—it means the exact same thing as an event.
Now, a runner is simply a virtual server instance—it's where your automation is going to be performed when that workflow is triggered. You can utilize either the public runners that GitHub provides, or if you have a very special use case, you can also set up your own self-hosted runner. If you choose the public runners, you can choose from macOS, Windows, and Ubuntu in various versions. One thing to watch out for—be aware of with the public runners—is that they are public, meaning that when you want to run your workflow, it may not occur immediately because you may be placed into a queue waiting for a public runner to become available. So, just be aware of that.
A job then is simply a collection of steps—it's where we're going to define all of our business logic and automation that we want to have occur. They are going to be executed in that runner—in the same runner—so you're not going to have one step perform on one runner and then the next on a different runner—they're always going to run on that same runner. We tell the GitHub Actions platform which runner we want to run on via the runs-on
property for the job. So, we say, we've got this job, I want this to run on macOS 10.12. Now, jobs by default inside of your workflow are going to run in parallel—they're all going to run at the exact same time. If we need to, we can define dependencies between those jobs, but it's important to remember—by default, until you set up that dependency—they're all going to kick off at the exact same time. Now, technically, a workflow can have an unlimited number of jobs. There is an asterisk next to that "unlimited" because there are limits on how long a job can run and how many jobs you can run in parallel, so with those limitations, you might not be able to truly have an unlimited number.
A step then is simply a shell script or a call to an action that we want to have performed. They are executed in order—they do not run in parallel—but they are executed one at a time and are dependent one on the other. So, by default, if an earlier step fails, it's going to fail out all the rest of the steps in that job. Again, if we need to, we can change that default behavior, but that's how it operates by default. And again, all those steps—and this is important—all the steps run in the same runner. You don't have step one run in Ubuntu and step two run somewhere else—that's not going to happen. We can set up jobs to run on multiple versions of runners, but by default, those steps are all going to run on the exact same one.
As I mentioned earlier, an action is simply that self-contained piece of automation, like a Lego block, that we can then combine with other actions to build out complex automation tasks. So, digging into the configuration of that workflow file - there are some things that you have to have to build out this automation. You have to have an event. So, inside of that - inside of your workflow file - we have to define at least one event to let the platform know when we want or how we want this workflow to be triggered. Inside of there, we have to have at least one job defined, and then that job has to have at least one step. The steps themselves either have to run a shell command or utilize some action.
So, we're going to build out our first workflow. Let me flip over here to my screen, and this is where—if you can't see the script, please let me know, and I'll try to blow up the font a little bit more. So, what is one of the things we have to have? An event. Okay, so I'm going to use the on
keyword. Now, I'm going to give a shout-out to JetBrains for making a wonderful collection of IDEs. I have a plugin to help me build out workflows, so you can see as soon as I start that keyword, it automatically pre-populates it, and now I have a ton of different events that I can utilize. For today’s, I'm going to utilize the workflow_dispatch
. The workflow_dispatch
allows me to run a workflow manually, so I can go in and trigger it on my own whenever I need to, instead of having to try to recreate an existing event. What's another thing we have to have? We have to have a runner. But where do we define the runner? What's that? I think I heard it—jobs. Okay, so I'm going to do jobs
, and I’ve got to create a job. So, I'm going to say this one's called "say hello," and then what do you say—we need a runner? So, I'm going to say runs-on
, in this case, I'm going to say ubuntu-latest
. Then, what else do I need inside the job? Steps. So, I'm going to define some steps, and in this case, I'm going to say run: echo “hello there”
—not "Jello there," how about "hello there"? Yeah, there we go. All right.
Now, to run your actions—oops, that's the wrong tab. Let's try this one—there we go—that is also the wrong one. Hey, there we go! To run these, you're going to run these inside the actions tab in your repository. If you have your computer out and you'd like to see this, the repository itself is pinned up here to the top of my profile. My profile is at github.com/gillo—my last name. I have an unusual last name. So, inside that repository, over here, I have an actions tab, and this is where all of my various actions—there on the left—are going to be defined. We just created one called "first." Now, I'm not going to run it because I don't want to wait for a public runner to come up, but I'll show you it being run. By default, it'll show you the workflow's name of the file inside—it will then list out all the jobs—so there's that "say hello" job. Then inside of that, as we dig down, it will list out the steps that are running—we just ran one, which was to say "hello there." No applause—yay! We did it! We built our first workflow—fantastic.
So, let's dig a little bit deeper, because I did utilize some pieces that I haven't talked about yet. One of those is the job ID. So, every job has to have a job ID. In the example we just did, I called it "saycore hello." That's because one thing it has to have is it has to be alphanumeric characters, dashes, or underscores. They have to be unique inside the workflow. So, we can have as many jobs as we want, but they all have to have unique IDs, and then they must start with either a letter or an underscore. And again, inside those steps that we have, it's just an array of tasks—each one of those that we want to run. It is important to remember that each step is going to run inside of its own process. So, just like if you were to create a subprocess in a shell command, it's going to be in its own process or start up a new session—they're each going to run inside their own process. But they all have access—because they're running on the same runner—they all have access to the same file system. So, if an earlier step writes to a file, a later step will have access to that because it has access to the file system.
Each step individually then has to include either the uses
property, which says, "Hey, GitHub Actions, I want to use an existing action—I want you to pull that in and execute that," or, like we did, we used the run
property, which says, "Hey, I want to run this shell command," or "I want to run this shell script." All right, so let's look at our second workflow. So, flip back over here—and that should be—oops, scoot over—I'm on the wrong one—there we go. So, in this case, I've got my event, so on workflow_dispatch
, I've got the same exact job, "say hello," but notice I've given it a name. So, one of the pieces that you can utilize inside your workflows is you can give the workflow a name, so it has a nice label in that actions tab. You can give each job a name
property, so again, you have that nice job name instead of just the ID—you can even give your steps names. But now, in this case, I'm running one "hello there"—I then say, "Hey, use some action." So, I'm showing you this one because, by default, unlike GitLab, GitHub Actions does not check out your repository into the runner when you start it up. If you need access to your repository's code, you have to use the checkout
action to say, "Check out my repository into this file system." So, in this case, I've actually checked out this repository into the file system, which is what allows me, in the third step, to cat
out that list of files—and then in the fourth step, I'm just running—"I am step four." So, let's take a look at that one. Come back over here—come back—I will go back to my actions. Now, notice on the left—there it is—"welcome to the party," second there on the left—click on that. If I dive into it, now notice the job is no longer the job ID, but that nice label. And now inside here, I've got my four steps, with that third one having that nice, more human-readable label. You are a tough crowd—no applause for each of these—wow, all right. Man, there we go—that's what I'm looking for.
All right, so you may be asking yourself, "All right, Paul, we're halfway through this presentation, and so far, you've spent half of it talking about workflows. I thought this was about actions—why is that?" Anybody wondering that? Yeah, okay, so even if we were to build an action in the beginning, you cannot use an action directly—you can only use an action through a workflow. So, if you don't know how workflows work, you can't use actions. The second piece is that an action—a composite action, which we'll talk about the different types in a second—a composite action works almost identically to a workflow with the workflow_dispatch
event. They're very similar—they share a lot of things, such as inputs. So, as you're working, as you're building out your business logic, one of the ideas behind an action is that it's reusable, right? You can use it in multiple instances. So, there's a very good chance you're going to need to be able to ingest or accept information into that action, and that's what the inputs are for. But workflow_dispatch
can use those too, so we can use either inputs in a workflow_dispatch
or inside of our action, and we can define all those inputs that we want. And just like with jobs, they have to have unique IDs, and again, be alphanumeric.
Now, inside an action itself, there are some differences. One of those is that you have to include a description for every input inside of an action. workflow_dispatch
, you can include them, but you don't have to—but in an action, it's required. Another piece that you can utilize is the required
property—whether or not that input is required in order for the workflow or the actions to run. And last is you can set up some default values, so if they don't provide that value or provide a value for that particular input, you can default to some known good value. You access what is input via something called a "context," which I'm going to talk about next. So, let's look at an input. So, flip back over—there we go. So, now notice here—I've got on: workflow_dispatch
. Now I have an inputs
property. Inside the inputs
property, I've given one of those inputs a name, I've set up the description, and I've also said it's true. I mentioned earlier we're going to access that input via a context—that's what this little piece is down here. This says, "Hey, go back to the inputs—now grab the value from the ID of the input—the name." Make sense? All right, let's see it in action.
So, come back over—I'll come back up to the third one. In this case, I'll actually run this one. I mentioned earlier you can run workflows via that workflow_dispatch
—that's how this little button on the right shows up. And notice—what does it say? "What's your name?" There's that input, and if I try to run it—what's—hey, thank you, thank you—so if I try to run it, what should happen? Fails, yep—it says, "No, that's required." So, I'll say, "Jerome—hello, Jerome—woohoo!" So, I'm going to run that workflow—hopefully there's a public runner pretty quick. Come on, public runners—I'll know I have a public runner running it because that little check mark will change into a dot—hey, there it went! I can now go into there—have my job listed—it is currently running or waiting—there, it's requesting—come on, finish up, finish up—hey! All right, there it is—there we go, thank you, thank you all.
All right, so, context—contexts are how your actions, your workflows receive information from the actions platform, whether that's about runners, about the job, the workflow itself, the events that are occurring—it's how that information is exposed back to your codebase. Now, you do have 12 types of contexts—I'll show you those here. Here on the right, you can see we've got 12 different types. So again, the runners, the steps, the inputs, the needs, matrix, secrets, variables, etc.—all different types of information available to you. One thing to note, though, is that not all contexts are available in all situations. So, there is something called a "matrix" that you can utilize. If you're not using a matrix, well then, the matrix context isn't available. So, just realize that those aren't all available to you at all times. To access a context, if you're using anything that is not a JavaScript-based custom action using the GitHub toolkit, you will access these contexts with the dollar sign—the, what do you call them, the fancy brackets—the fancy curly braces, I don't know what they're actually called, whatever they're called—the context name, the property name that you want. Then the properties themselves can either be the string values that you're looking for, or they can be other objects, and you can dig further and further and further down until you get to the context that you need, such as accessing a steps context where I access a specific ID of a step, then I target its outputs, which we're going to talk about—outputs in a second—I target its outputs context to get the value contained in the target URL ID.
If you're using JavaScript, they have this nice little toolkit that then exposes those various contexts via methods. Now, there is a quick warning, and this is an important warning—please, if you don't remember anything else about this presentation, please remember this—the contexts that come from user-supplied input are not pre-escaped—they're not prepared for you to utilize inside of shell commands or your code directly. All right? This means they are ripe for code injection. As an example, if I were to try to set a shell variable equal to a request title—a pull request title—pull request titles come from users—then what would happen is if they created a pull request title called a”;
, then when I set title=“a”;
, then what happens? That’s a complete shell command, right? That semicolon says, "This is the end of one command—here's the next one." I would end up running this command. So, just be aware that these are not ready for you to use directly.
All right, so outputs—if you've accepted some input, you've done some processing on that information, you may need to output information. In addition, if you're building out a complex workflow, at some point, one of those steps is probably going to do something that you then want another step to be able to access. So, outputs are simply that—that's data or some information that you want to send from an action back to the calling workflow, or from some step out to the next step. So, it's just information that we can then reutilize later on. To set an output—again, from anything that is not using the JavaScript toolkit—you echo the ID equals the value and then send it over to the GitHub output environmental variable. Or, if you're using the JavaScript toolkit, they have a nice setOutput
method exposed for you to set those values. Then, as we saw earlier, to use those, you're going to access some context—the steps—most likely the steps—the step ID that outputs object from up here, and then that ID that we might have set.
Do you want to see it in action? Yay, there we go! All right, I should be—I was going to say, you should be very awake by now. All right, so we have another workflow here—oh, I'm going to scroll up because I did make the font a little bit bigger. So, notice here in the third—second step, excuse me—the second step, I'm taking the current timestamp of the date—I'm then going to set that to an ID of currentTime
, send out to the output, and then in the third step, I then can access that value that I set via the steps context—goes back up to get time, grabs currentTime
to output that. And I'll show you that in action. Flip over here, go back to my actions tab—did I hear a yawn? I'm not that boring, am I? Jeez, you guys are hard. Uh, let's see—is that third—I forgot which—oh, there it is. So, dig into here—here's my "say load" job, and also grab the time. So, here you can see that I have set the time, I echoed that out to the output. Let me grab my pointer here. So, I set currentTime=
the date—then in the fourth step, I output that via that context. Yay, there we go—that's what I'm looking for.
All right, now we are prepped. We have all the tools—we have all the knowledge needed. We're ready to begin to build custom actions—oh, except not actions with a capital "A"—we have to build actions with a lowercase "a," remember that. All right, so there are three types of actions that you can build. We've got access to a Docker type action, a JavaScript action, and a composite action. The difference is that Docker allows you to be very fine-grained in the environment in which your business logic is running. So, if you have very strict requirements, you're probably going to need to utilize your own Docker image instead of one of the public runners. A JavaScript-based composite action is nice because you can kind of separate that action code—that business logic—from the action service itself, and be more able—more able—easily—no—easily—more easily able—more easily able—I got my words jumbled—more easily able to mock up the environment and test that code outside of the GitHub Actions platform, and it typically runs a little bit faster than a Docker-based custom action. The problem with a JavaScript-based custom action is that you can't utilize or reuse somebody else's public action unless they've also exposed that action as a JavaScript package. So, that's what the third one's available for—the third one's called composite, and it's what allows you to use other people's actions inside of your own action, again allowing you to utilize that more Lego-block modularity to put things together into something more complex.
Now, the actions themselves must be stored in a repository all by themselves, or if you want to store them in an existing repository, they have to be inside their own directory. Now, there are some pieces and parts of actions that you have to have—excuse me—and the first is they all have to have a metadata file. That metadata file has to be a YAML file, and it has to be named action.yml
, and it must be stored in the root of the repository of the directory where your action is being stored. It has to contain at least three properties—four technically. It has to have a name, it has to have a description, and it has to have the runs
property with the runs-using
subproperty. It must have all of those. And it's that runs-using
that tells the actions platform what type we're going to use—so runs-using: docker
, runs-using: composite
. Depending on which type of action you choose, there are other properties that are required inside that runs
property. In addition, in the metadata file is where we define those inputs and outputs.
So, a sample action—and this is a complete action file—is: I've got my name
property, "Greets user," I've got description: Greet user to the GitHub Actions platform
, then I've got the runs
property with a subproperty using
, and I'm using node:20
to designate a JavaScript-based custom action. And then because I'm doing a JavaScript-based custom action, I then have to have the main
property and point it to my bootstrap or my kickoff file for my business logic inside of that JavaScript package
Now, there are a couple of warnings with the three different types of composite actions or three different types of actions. When you use the runs-using
property for Docker and composite, you use the words "Docker" and "composite," but for JavaScript, you don't use the word "JavaScript"—you use the node version that you want to use. Right now, only version 16 and 20 are valid for Node inside of a custom action—16 was deprecated as of October 24th. There is no sub-replacement yet, so the only one you really have access to is 20. I believe Node 22 is going to be released this coming Spring, and most likely they'll then add Node 22 as an option at that point. The other challenge with a JavaScript-based custom action is you have to commit the entirety of the node_modules
directory, or all of your JavaScript dependencies, into that repository in order to use them, or use something like Vercel's NCC to bundle all of your dependencies into a single JavaScript file.
The warning with the composite-based custom action is because you're using those runners, the binaries and programs inside the runners may not be the version that you need, so you may find yourself needing to update versions before you can utilize some shell command. And at that point, you know, if you're spending the bulk of your custom action—that composite action—updating dependencies, that may be the point at which you need to switch to that Docker-based. So, you ready to build a custom action? Yay, there we go—that's what I like to hear.
All right, so what do I have to have—oh, oh, you're good—all right, for an action, what do we have to have to build the action? Metadata file—what do we need? A name—okay, so I got my name. What else? Description—what else? Runs-using
. All right, so I got composite. In this case, composite is going to utilize other actions, so it's going to have a requirement of steps. In this case—forgot to mention this—I'm building this action over here inside the .github/actions
directory, and then it has a directory all its own. Now, it doesn't have to be "actions"—I just made that—it just made sense to me—I was going to store them all in a similar location, but my metadata file is in the root there.
So, what do I have to do in order to be able to utilize the repository that we saw earlier? Oh, I have to check it out, right—oops, I skipped a step—oh, I skipped all the way—I'm sorry, I skipped my steps. So, we're not going to—we're going to do that in the workflow, sorry. So, one of the things—we're trying to recreate this action into that workflow we did earlier. So, what I want to do is I want to do the same thing—I want to say "hello" to somebody, so I'm going to have an input. This time, I'm going to call it who_to_greet
, set up the description. We can skip the type—only act—every type inside of an action is a string. Inside workflows, you have multiple types, so in this case, I've just reused that. Now, in my step, I've given it a name, I've given it an ID—in this case, I've said I want to use the bash shell instead of some other type of shell, and then I'm going to run: echo "Hello" ${{ inputs.who_to_greet }}
.
Now, can we run this action directly? No, what do we have to have? A workflow—all right. Inside the workflow, what do we have to have? An event. All right, so there's my event—what else? Jobs. So, there's my job—then what else? Steps. Now, because I have stored this action in the repository itself, now I need to check out the repository and be able to use that action. Then I can say, "All right, I want to use the action that is stored in this repository," but this action that we just created has a—has a what? An input. So, I'm going to use the keyword with
and then I'm going to use that same input ID and then give it some value. Because I want to run this as a dispatch, I'm probably going to need to provide an input, so I'll also include an input on the workflow itself. So, the workflow is going to run, it's going to have an input, I'm then going to take what is input there and send it to the action that we just created.
Are you ready to see this in action? Yeah? All right, so let's go back—back to my actions tab, and we'll say, "V, an action" going—I hit run. Notice there's who we should greet—again, I'll say "SymfonyCon 2023," but I'll put some more exclamation marks in here—no, right there—oh, go all the way over—why won't it let me select it? There it goes—we'll say, "Super excited"—run that workflow—come on, public runners—because I know I'm close on time—come on, come on, come on—there we go—click on that. Now, that's our workflow—we dig into the workflow—we can see that it checked out our repository—it then ran our greet user—and then if I run that, I should have—there we go! Yay!
All right, so what did we cover? We covered the actions platform, right? We talked about the different components of the actions platform that we can use. We talked about the workflow file and how we define the workflow file and the pieces that are parts that are required of it. We then built our first workflow together. We talked about custom actions and the parts that are required for it. We then built that action together with that workflow and talked about some warnings, right? But, hello worlds are always a little, um, not quite as useful, correct? So, let's put everything together. Let's put all this together into a real-world example. And the goal is—in this particular case was a real goal—is we needed on every pull request—we needed to be able to run a visual regression test. Is everybody familiar with visual regression testing, where you take a screenshot of your production site and then a screenshot of some other site, and then you diff them and see if there's any regressions in that visual aspect, right? Good, yeah? Okay, so let's take a look at that action—oops, scroll over—Is that the action? No, that is not the action. Ah, there's the action—sorry, the wrong one.
All right, so what are—what are the minimum of two things that I need for a visual regression test? Two—I need a screenshot—I need the production URL—a production site—and a test site. So here, I've got my name and my description—we have to have those. I've got inputs—one is the test URL, so give me a URL that you want to test against some new stuff. The reference URL is that production site. Let me scroll so you can see this—I've got the runs-using: composite
and run-steps
. First one is—I probably want to make sure the URL—the string that I was given—looks like a URL, and I probably want to make sure the site is responding. So, in this case, I'm going to use an action—there it is—I've got an action, and I'm going to send it the test URL, and all that action does is say, "Does this look like a URL? Now let me call the site and make sure it responds." So, I do that once with the test URL, and then once with the reference URL.
Then I'm going to utilize a package called Backstop, and Backstop is a visual regression tool, so I install it. Now, in a minute, I'm going to need to change the configuration of Backstop to give it these new URLs. To do that, I need to use jq, a JSON query inside the shell, but the version that came in the public runner was 1.6, and I needed 1.7. So now I can use another action inside my action to update jq inside the runner. I then update that configuration for Backstop to include my new URLs, then I run Backstop's reference that goes out and takes those screenshots. Then I can come down and run the test.
Now, what does a test usually do if there's a failure? It fails, there's one—but what else? It creates a report, right? So, two things: One, it fails—and I said earlier steps are always dependent on each other—if one fails, everything else fails out. I don't want that to happen. So, in this case, notice I've set continue-on-error: true
—so that says, "Don't fail out if one of these steps fails." I suppress the message, and I capture it—there it is—test_results=$?
. And then I come down and—I—oops, it's not on screen—let me scroll down a little lower—I set the output of the test itself to another environmental variable that I can then utilize down lower.
Now, I did say earlier that each of the steps are dependent—in this case, notice you also have conditionals for steps. I can say, "Only run this step if that environment variable has been set to false." So, if the test failed earlier, I'm now going to utilize yet another action to store the report and attach it to the calling workflow, so that way somebody that's utilizing this in a pull request can see that failure and actually utilize or view that visual regression test. Then, lastly, if it fails, then I'm going to exit out with a non-zero, so that fails the step in the calling workflow, and then we'll fail out the rest of those steps in that job.
To see it—flip over here—here is the attached workflow that goes with it. So now I'm going to say, on pull request target to main—so if there's a pull request against main, I'm going to run on Ubuntu's latest. I'm then going—oh, I almost forgot—one of the great things about Platform and Upsun is we have those integrations with GitHub, and one of the things that it does is when you create a pull request, it will actually clone your production environment. It then overlays that PR code into that environment and then brings it up. With the integration, it will contact GitHub and say, "Hey, I've successfully finished this—here is your new ephemeral PR environment URL." And so that's what this first action does—it says, "Hey, have you received a success yet, GitHub? Oh, you have? Okay, now give me that URL," and it sends an output called target_url
. So then, the second step is to run that action I just showed you, sending that URL we got from the first action. And then I've stored—because your production URL usually doesn't change—I've stored that as a repository variable inside GitHub, passing that over.
To see it in action, let me flip back over because I know I'm short on time. Where is my pull request—no, that's context—there it is—here's my pull request. You can see down below, there's that integration—it was successful—it gave back my URL. I've got some other tests running, then the workflow for the pull—the visual regression testing ran, but you can see it failed. If I hit the details, now it'll say—there it is—you see, oh, visual regression testing failed. Then if I look in the summary—if I scroll—if it'll let me scroll all the way down at the bottom—there is that stored visual regression testing report. And now I can take a look at that report and see I had two passed, two failed—oh goodness, yes, this definitely was not good. Oh, looks like maybe the font size changed or something—oh yeah. And now I know in my pull request, “Hey, something's wrong—I need to go check to see what happened.”
Thank you all. All right, I think I am out of time—might have two, three—I don't know if I have time left or not—I didn't see what time it is. Well, this is my contact information. I'm kind of easy to find—there's not many Gilzow in the world. But the best way to find me is linktr.ee/gilzow—that's got all my social media accounts—every single one, profiles everywhere, and I'm almost always "gilzow" at everything. So, feel free to contact me—I'll also be at the booth the remainder of the day—happy—I love talking about this stuff, so would be happy to entertain any questions you have either now or later.