Use the test pyramid as mentioned by Peter Masiar.
My contribution here is help answer the question of exactly how to decide the 20% and 80% figures that Peter mentions.
My approach to this is to make sure that the UI testing focuses on the functionality of the UI itself. In other words, from a user perspective the text and links are correctly displayed and - in particular - HTML forms and input fields work as expected.
The way to avoid having a large number of UI cases is to avoid what I have come to term 'data combinatorial testing'. By this I mean any test that says 'given this extra piece of data "x", I should see "y". "x" can be data input by the user, data retrieved from a data store, data from an external service, etc. This should be tested on the back end.
An example will probably help:
A business operates in 50 states. Each State has a different welcome message targeted to the local culture. The business might ask 'can we have 50 tests please to make sure we have all the messages correct?'. It would be possible to write 50 browser UI tests, one for each state that verify the information. However, the correct way to do this would be to recognize that it isn't "the browser" that determine the text. It is something on the back end that says "when I am passed the text "Wyoming" (for example) I will return the text "Home of Yellowstone", etc. So we should write back end unit/integration tests that test that each state passed to the routine that determines the message results in the correct test. Then we test one state in the browser to make sure that the lookup works.
Given that browser tests might take 30 seconds each (including some navigation to get the page in question) that would mean 50 x 30 would be 1500 seconds or 30 minutes test run time. Whereas a unit test might run in 1 second (or much less), so (30 * 1) + 30(for 1 browser test) = 1 minute test time - 1/30 of the time when compared to 30 browser tests.
To the question of "Is API + Integration testing the best option for tests in CI", I don't think specifically so. CI is a good option for both unit/api tests as well as UI tests. In both cases you get the advantage of:
- run on a different machine which is always good
- parallelization and elastic clouds
- run tests on a known configuration
- ability to create an automated devops pipeline
The fact that UI tests are soooo slow and benefit so greatly from the parallelization benefit of CI, those are the ones I often want to run in CI. Also as Unit and Development tests are intended for instant feedback to the developer it feels more appropriate to make sure that these, at least initially run locally to be as 'close' as possible in the simplest way, e.g. no CI, local code. UI tests are often at a later phase or during UAT testing when there has been separation of time from the coding so the need to run them locally can be lessened.