Among the many things that we, the app team, had the opportunity to set up, the Continuous integration (CI) for mobile Apps, was one of the most exciting.

Let’s face it, nobody likes repetitions and other kind of boring tasks. Among them you can find things such as: Providing versions, moving jira tickets, uploading on stores etc…

So many things were already scripted to work on developers’ machines using Fastlane. (1)
But with the CI, a new world was opening to us and with it, opportunities.

Chapter 1:

Once upon an app
We did start back in the day put up something that could be called a CI. Sure it wasn’t pretty and all, but it was our first step on the journey.

Figure 1 : Our first CI, Jenkins using a physical device to build apps then pushing them on the stores and or on our developpement tool such as fabric and jira

After a few aborted attempts with a remote iMac & Jenkins , it was working, yet it was not stable. The machine was occasionally shutting down, the Apple auth was expiring often, we had to maintain the tech stack up to date on this computer, and on this specific use case performance was getting slower over time with the growth rate of the project, requiring us to upgrade it.

Chapter 2:

A new era.

At one point in time La Redoute decided to start a collaboration with GitLab, and aside from the annoying task to migrate all projects, we got there a nice opportunity to have Gitlab CI available to us.(2)

At this point, our CI took a turn and evolved. We replaced our Jenkins for gitlab CI and and started efforts to move from physical devices to a combination of dockers and Kubernetes (3)

Figure 2 : Our CI using gitlab ci, docker and kubernetes to build and push apps builds

This was providing a much faster and a much more stable CI. From there we could finally start to build something strong !

Chapter 3:

When things got serious

It was time to go further, to automate more, always more, if not everything!!

First things first, we needed to rethink our ways of working and so we planted a new tree.

GIT HUB FLOW (Or at least something close)
We took our repo, changed few branchs, names, created some new habits and synchronized with our testing team.(4)

Why? You might ask.

* All merged code is reviewed by peers and validated by testers
* Having ALWAYS a ready to deploy branch
* Pushing more often and lighter versions
* Better Crash free on apps

Each point being a consequence of the previous one. For our needs it was a better suited flow
Giving something as simple as that :

Figure 3 : Github flow feature beeing merged, only when reviewed and validated by QA

Now we have:

• A Ready branch, that contains only ready feature, meaning : Reviewed and tested!

• A feature / fix or any kind of working branch, where the dev can work on.

• And the merge process, which starts of course by a Merge Request (MR), which implies peer reviews and here an approval from the QA (Quality assurance, our testing team)
“Yes, but what about the CI ? Well, this is where it gets interesting let’s add the CI in here

Figure 4 : CI added in our flow before QA validation and merges

Now we have a door to do the magic, which includes:

• Making sure the MR is Building, which is of course the most important, you don’t want to push something that won’t build on the ready branch, and ending on the wall of shame at the office.

• Running unit tests. Of course, QA will ensure that the feature is well within the specs, but we need also to check that we did not break anything on our side. You can then easily prevent anyone from breaking something before merging!

• Checking code quality. There, sure it’s optional but it’s a good practice. Whether you use predefined linter or team-built requirements, if you want your code base to last and have consistency over the whole project it’s strongly recommended. (5, 5.1,5.2)

• Generating a version for the QA. Well if you want an approval from your testing team then you’ll need to provide them with a build to test. Here there are many options, such as the well-known app test or testflight.

What you also can do, to reduce potential back and forth due to waiting the dev team approval before triggering the version generation, is to use scheduled pipelines (6). They would run at night for instance, to avoid consuming the CI bandwidth during your working hours or a merge request event when the MR is approved (7)

• Updating Jira ticket, well this is the cherry on top, there is an already existing link possible between Jira and Gitlab (8) for automated transition (which avoid people forgetting to update the ticket status), but using Jira API (9) within the CI you can also add comments, link builds and anything you would have in mind to smoothen your collaboration with the functional team.

Figure 5 Details of our CI including a linter, unit test, archive building and distribution and ticketing tool synchronisation

This new implementation is quite a good base, and we could be happy with this, but we wanted more!

Ok, what now?

We la Redoute, use automated tests campaigns to check our apps to locate potential Regressions.
These Campaigns are managed by Cerberus(10) and ran by Appium(11), using a farm of real devices or simulator depending on the use case. (12)
Back in the day, we used it only once per release, right before going live.

Of course, this was creating a painful process of back and forth between the automation team and the app team. Both to update the test campaigns but also to fix the regressions, especially with the version sizing we had. (More than 100 tickets per version)

So, what if…, we had Non Regression Tests (NRT) more often? Yes, alright, but we would need to send a stable build!
Well there we are, luckily, we do have a stable branch!

Figure 6 : A Daily job added on the stable branch to run automated tests campaigns, using apium and physical/virtual devices

Now here we are, running everyday non-regression tests, having continuous feedback! Leading to a faster release process!
But we could do something even better! What if we could catch the regression even before merging into our ready branch!

Figure 7 : Adding automated tests campaign within the merge request job, as part of the validation process

Now, by setting a webhook between Cerberus and Gitlab, we have direct feedback on each MR, whether we broke something in the app or not! You can also use the result of these campaigns as a requirement to be allowed to merge your MR

The only thing we need now is to deploy!

Figure 8 : Adding deployment to the stores in the release candidate CI job

There are many ways one can trigger a job , but we choose to use tags ! (12)

We tag our ready branch, and “ et voila “ the CI create a candidate release and generate the necessary versions for the stores and the non-regression test.
Now we just wait for the automation team’s feedback and …. damn we have a bug!

No worries, let’s explore this situation

Figure 9 : Process in case of a bug discovery during the deployment. A jira ticket is opened, the issue got fixed and tested then the CI restart once merged

We open a Jira bug.
We fix it using the same process as before. Then , the CI will redo the whole delivery.

And this process will repeat itself until we have a GO !

Finally, when this happens, we have nothing else to do but simply press the publish button on stores!

(One more thing…) Chapter 4:

The iOS case

Indeed, it wouldn’t be an app project if we did not had specific topics to tackle with Apple!
So as we pointed out in Chapter 1, we had issues with iOS from the start. The main one being that we needed a macOS runner to build the archive.

So we had to choose between staying on a physical machine, using Xcode Cloud, or an external partner.

We’ve given some try with a lot of hopes for Xcode Cloud, unfortunately we quickly reached limits, having ghost build, successful build but never to be found anywhere, unexpected errors, in short it was not good enough for us yet.

In the end we turned ourselves to bitrise to build our archives.

Figure 10 : Integration of Bitrise to build iOS archives

And now we rely on Bitrise for building and uploading the archive on Apple tools, such as TestFlight and the App store. But for the rest, we keep it as much as possible on Gitlab CI and common both for iOS and Android avoiding duplication in bitrise.

Glossary

[1] https://fastlane.tools/

[2] https://docs.gitlab.com/ee/ci/

[3] https://juanmercadoit.com/2023/05/11/how-to-create-a-secret-to-deploy-container-images-from-the-gitlab-registry

[4] https://medium.com/@yanminthwin/understanding-github-flow-and-git-flow-957bc6e12220

[5] https://mindsers.blog/fr/post/linting-good-practices/

[5.1] https://pinterest.github.io/ktlint/1.2.1/

[5.2] https://github.com/realm/SwiftLint

[6] https://docs.gitlab.com/ee/ci/pipelines/schedules.html

[7] https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#merge-request-events

[8] https://docs.gitlab.com/ee/integration/jira/

[9] https://developer.atlassian.com/server/jira/platform/rest-apis/

[10] https://cerberus-testing.com/

[11] https://appium.io/docs/en/latest/

[12] https://laredoute.io/blog/mobile-application-release-with-quality-and-speed/

[13] https://docs.gitlab.com/ee/ci/yaml/#only–except

Author