How to click a clickablespan using espresso?

This is my solution. It's simpler because we don't need to find the coordinates. Once we have found the ClickableSpan, we just click on it:

public static ViewAction clickClickableSpan(final CharSequence textToClick) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return Matchers.instanceOf(TextView.class);
        }

        @Override
        public String getDescription() {
            return "clicking on a ClickableSpan";
        }

        @Override
        public void perform(UiController uiController, View view) {
            TextView textView = (TextView) view;
            SpannableString spannableString = (SpannableString) textView.getText();

            if (spannableString.length() == 0) {
                // TextView is empty, nothing to do
                throw new NoMatchingViewException.Builder()
                        .includeViewHierarchy(true)
                        .withRootView(textView)
                        .build();
            }

            // Get the links inside the TextView and check if we find textToClick
            ClickableSpan[] spans = spannableString.getSpans(0, spannableString.length(), ClickableSpan.class);
            if (spans.length > 0) {
                ClickableSpan spanCandidate;
                for (ClickableSpan span : spans) {
                    spanCandidate = span;
                    int start = spannableString.getSpanStart(spanCandidate);
                    int end = spannableString.getSpanEnd(spanCandidate);
                    CharSequence sequence = spannableString.subSequence(start, end);
                    if (textToClick.toString().equals(sequence.toString())) {
                        span.onClick(textView);
                        return;
                    }
                }
            }

            // textToClick not found in TextView
            throw new NoMatchingViewException.Builder()
                    .includeViewHierarchy(true)
                    .withRootView(textView)
                    .build();

        }
    };
}

Now you can use our custom ViewAction just like that:

    onView(withId(R.id.myTextView)).perform(clickClickableSpan("myLink"));

The best option would be to subclass a ViewAction. Here is the way of doing it in Kotlin:

class SpannableTextClickAction(val text: String) : ViewAction {
    override fun getDescription(): String = "SpannableText click action"

    override fun getConstraints(): Matcher<View> =
            isAssignableFrom(TextView::class.java)

    override fun perform(uiController: UiController?, view: View?) {
        val textView = view as TextView
        val spannableString = textView.text as SpannableString
        val spans = spannableString.getSpans(0, spannableString.count(), ClickableSpan::class.java)
        val spanToLocate = spans.firstOrNull { span: ClickableSpan ->
            val start = spannableString.getSpanStart(span)
            val end = spannableString.getSpanEnd(span)
            val spanText = spannableString.subSequence(start, end).toString()
            spanText == text
        }
        if (spanToLocate != null) {
            spanToLocate.onClick(textView)
            return
        }
        // textToClick not found in TextView
        throw NoMatchingViewException.Builder()
                .includeViewHierarchy(true)
                .withRootView(textView)
                .build()
    }
}

and use it as:

onView(withId(<view_id>)).perform(scrollTo(), SpannableTextClickAction(text))

Here is the Kotlin version of accepted answer

fun clickClickableSpan(textToClick: CharSequence): ViewAction {
    return object : ViewAction {

        override fun getConstraints(): Matcher<View> {
            return Matchers.instanceOf(TextView::class.java)
        }

        override fun getDescription(): String {
            return "clicking on a ClickableSpan";
        }

        override fun perform(uiController: UiController, view: View) {
            val textView = view as TextView
            val spannableString = textView.text as SpannableString

            if (spannableString.isEmpty()) {
                // TextView is empty, nothing to do
                throw NoMatchingViewException.Builder()
                        .includeViewHierarchy(true)
                        .withRootView(textView)
                        .build();
            }

            // Get the links inside the TextView and check if we find textToClick
            val spans = spannableString.getSpans(0, spannableString.length, ClickableSpan::class.java)
            if (spans.isNotEmpty()) {
                var spanCandidate: ClickableSpan
                for (span: ClickableSpan in spans) {
                    spanCandidate = span
                    val start = spannableString.getSpanStart(spanCandidate)
                    val end = spannableString.getSpanEnd(spanCandidate)
                    val sequence = spannableString.subSequence(start, end)
                    if (textToClick.toString().equals(sequence.toString())) {
                        span.onClick(textView)
                        return;
                    }
                }
            }

            // textToClick not found in TextView
            throw NoMatchingViewException.Builder()
                    .includeViewHierarchy(true)
                    .withRootView(textView)
                    .build()

        }
    }
}